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.indentation;
7 
8 import dparse.lexer;
9 
10 import std.bitmanip : bitfields;
11 
12 /**
13  * Returns: true if the given token type is a wrap indent type
14  */
15 bool isWrapIndent(IdType type) pure nothrow @nogc @safe
16 {
17     return type != tok!"{" && type != tok!"case" && type != tok!"@"
18         && type != tok!"]" && type != tok!"(" && type != tok!")" && isOperator(type);
19 }
20 
21 /**
22  * Returns: true if the given token type is a temporary indent type
23  */
24 bool isTempIndent(IdType type) pure nothrow @nogc @safe
25 {
26     return type != tok!")" && type != tok!"{" && type != tok!"case" && type != tok!"@";
27 }
28 
29 /**
30  * Stack for managing indent levels.
31  */
32 struct IndentStack
33 {
34     static struct Details
35     {
36         mixin(bitfields!(
37             // generally true for all operators except {, case, @, ], (, )
38             bool, "wrap", 1,
39             // temporary indentation which get's reverted when a block starts
40             // generally true for all tokens except ), {, case, @
41             bool, "temp", 1,
42             // emit minimal newlines
43             bool, "mini", 1,
44             // for associative arrays or arrays containing them, break after every item
45             bool, "breakEveryItem", 1,
46             // when an item inside an array would break mid-item, definitely break at the comma first
47             bool, "preferLongBreaking", 1,
48             uint, "",     27));
49     }
50 
51     /**
52      * Get the indent size at the most recent occurrence of the given indent type
53      */
54     int indentToMostRecent(IdType item) const
55     {
56         if (index == 0)
57             return -1;
58         size_t i = index - 1;
59         while (true)
60         {
61             if (arr[i] == item)
62                 return indentSize(i);
63             if (i > 0)
64                 i--;
65             else
66                 return -1;
67         }
68     }
69 
70     int wrapIndents() const pure nothrow @property
71     {
72         if (index == 0)
73             return 0;
74         int tempIndentCount = 0;
75         for (size_t i = index; i > 0; i--)
76         {
77             if (!details[i - 1].wrap && arr[i - 1] != tok!"]")
78                 break;
79             tempIndentCount++;
80         }
81         return tempIndentCount;
82     }
83 
84     /**
85      * Pushes the given indent type on to the stack.
86      */
87     void push(IdType item) pure nothrow
88     {
89         Details detail;
90         detail.wrap = isWrapIndent(item);
91         detail.temp = isTempIndent(item);
92         push(item, detail);
93     }
94 
95     /**
96      * Pushes the given indent type on to the stack.
97      */
98     void push(IdType item, Details detail) pure nothrow
99     {
100         arr[index] = item;
101         details[index] = detail;
102         //FIXME this is actually a bad thing to do,
103         //we should not just override when the stack is
104         //at it's limit
105         if (index < arr.length)
106         {
107             index++;
108         }
109     }
110 
111     /**
112      * Pops the top indent from the stack.
113      */
114     void pop() pure nothrow
115     {
116         if (index)
117             index--;
118     }
119 
120     /**
121      * Pops all wrapping indents from the top of the stack.
122      */
123     void popWrapIndents() pure nothrow @safe @nogc
124     {
125         while (index > 0 && details[index - 1].wrap)
126             index--;
127     }
128 
129     /**
130      * Pops all temporary indents from the top of the stack.
131      */
132     void popTempIndents() pure nothrow @safe @nogc
133     {
134         while (index > 0 && details[index - 1].temp)
135             index--;
136     }
137 
138     bool topAre(IdType[] types...)
139     {
140         if (types.length > index)
141             return false;
142         return arr[index - types.length .. index] == types;
143 
144     }
145 
146     /**
147      * Returns: `true` if the top of the indent stack is the given indent type.
148      */
149     bool topIs(IdType type) const pure nothrow @safe @nogc
150     {
151         return index > 0 && index <= arr.length && arr[index - 1] == type;
152     }
153 
154     /**
155      * Returns: `true` if the top of the indent stack is a temporary indent
156      */
157     bool topIsTemp()
158     {
159         return index > 0 && index <= arr.length && details[index - 1].temp;
160     }
161 
162     /**
163      * Returns: `true` if the top of the indent stack is a temporary indent with the specified token
164      */
165     bool topIsTemp(IdType item)
166     {
167         return index > 0 && index <= arr.length && arr[index - 1] == item && details[index - 1].temp;
168     }
169 
170     /**
171      * Returns: `true` if the top of the indent stack is a wrapping indent
172      */
173     bool topIsWrap()
174     {
175         return index > 0 && index <= arr.length && details[index - 1].wrap;
176     }
177 
178     /**
179      * Returns: `true` if the top of the indent stack is a temporary indent with the specified token
180      */
181     bool topIsWrap(IdType item)
182     {
183         return index > 0 && index <= arr.length && arr[index - 1] == item && details[index - 1].wrap;
184     }
185 
186     /**
187      * Returns: `true` if the top of the indent stack is one of the given token
188      *     types.
189      */
190     bool topIsOneOf(IdType[] types...) const pure nothrow @safe @nogc
191     {
192         if (index == 0)
193             return false;
194         immutable topType = arr[index - 1];
195         foreach (t; types)
196             if (t == topType)
197                 return true;
198         return false;
199     }
200 
201     IdType top() const pure nothrow @property @safe @nogc
202     {
203         return arr[index - 1];
204     }
205 
206     Details topDetails() const pure nothrow @property @safe @nogc
207     {
208         return details[index - 1];
209     }
210 
211     int indentLevel() const pure nothrow @property @safe @nogc
212     {
213         return indentSize();
214     }
215 
216     int length() const pure nothrow @property @safe @nogc
217     {
218         return cast(int) index;
219     }
220 
221     /**
222      * Dumps the current state of the indentation stack to `stderr`. Used for debugging.
223      */
224     void dump(size_t pos = size_t.max, string file = __FILE__, uint line = __LINE__)
225     {
226         import dparse.lexer : str;
227         import std.algorithm.iteration : map;
228         import std.stdio : stderr;
229 
230         if (pos == size_t.max)
231             stderr.writefln("\033[31m%s:%d %(%s %)\033[0m", file, line, arr[0 .. index].map!(a => str(a)));
232         else
233             stderr.writefln("\033[31m%s:%d at %d %(%s %)\033[0m", file, line, pos, arr[0 .. index].map!(a => str(a)));
234     }
235 
236 private:
237 
238     size_t index;
239 
240     IdType[256] arr;
241     Details[arr.length] details;
242 
243     int indentSize(const size_t k = size_t.max) const pure nothrow @safe @nogc
244     {
245         import std.algorithm : among;
246         if (index == 0 || k == 0)
247             return 0;
248         immutable size_t j = k == size_t.max ? index : k;
249         int size = 0;
250         int parenCount;
251         foreach (i; 0 .. j)
252         {
253             immutable int pc = (arr[i] == tok!"!" || arr[i] == tok!"(" || arr[i] == tok!")") ? parenCount + 1
254                 : parenCount;
255             if ((details[i].wrap || arr[i] == tok!"(") && parenCount > 1)
256             {
257                 parenCount = pc;
258                 continue;
259             }
260 
261             if (i + 1 < index)
262             {
263                 immutable currentIsNonWrapTemp = !details[i].wrap
264                     && details[i].temp && arr[i] != tok!")" && arr[i] != tok!"!";
265                 if (arr[i] == tok!"static"
266                     && arr[i + 1].among!(tok!"if", tok!"else", tok!"foreach", tok!"foreach_reverse")
267                     && (i + 2 >= index || arr[i + 2] != tok!"{"))
268                 {
269                     parenCount = pc;
270                     continue;
271                 }
272                 if (currentIsNonWrapTemp && (arr[i + 1] == tok!"switch"
273                         || arr[i + 1] == tok!"{" || arr[i + 1] == tok!")"))
274                 {
275                     parenCount = pc;
276                     continue;
277                 }
278             }
279             else if (parenCount == 0 && arr[i] == tok!"(")
280                 size++;
281 
282             if (arr[i] == tok!"!")
283                 size++;
284 
285             parenCount = pc;
286             size++;
287         }
288         return size;
289     }
290 }
291 
292 unittest
293 {
294     IndentStack stack;
295     stack.push(tok!"{");
296     assert(stack.length == 1);
297     assert(stack.indentLevel == 1);
298     stack.pop();
299     assert(stack.length == 0);
300     assert(stack.indentLevel == 0);
301     stack.push(tok!"if");
302     assert(stack.topIsTemp());
303     stack.popTempIndents();
304     assert(stack.length == 0);
305 }