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