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         index = index + 1 == arr.length ? index : index + 1;
72     }
73 
74     /**
75      * Pops the top indent from the stack.
76      */
77     void pop() pure nothrow
78     {
79         index = index == 0 ? index : index - 1;
80     }
81 
82     /**
83      * Pops all wrapping indents from the top of the stack.
84      */
85     void popWrapIndents() pure nothrow @safe @nogc
86     {
87         while (index > 0 && isWrapIndent(arr[index - 1]))
88             index--;
89     }
90 
91     /**
92      * Pops all temporary indents from the top of the stack.
93      */
94     void popTempIndents() pure nothrow @safe @nogc
95     {
96         while (index > 0 && isTempIndent(arr[index - 1]))
97             index--;
98     }
99 
100     bool topAre(IdType[] types...)
101     {
102         if (types.length > index)
103             return false;
104         return arr[index - types.length .. index] == types;
105 
106     }
107 
108     /**
109      * Returns: `true` if the top of the indent stack is the given indent type.
110      */
111     bool topIs(IdType type) const pure nothrow @safe @nogc
112     {
113         return index > 0 && index <= arr.length && arr[index - 1] == type;
114     }
115 
116     /**
117      * Returns: `true` if the top of the indent stack is a temporary indent
118      */
119     bool topIsTemp()
120     {
121         return index > 0 && index <= arr.length && isTempIndent(arr[index - 1]);
122     }
123 
124     /**
125      * Returns: `true` if the top of the indent stack is a wrapping indent
126      */
127     bool topIsWrap()
128     {
129         return index > 0 && index <= arr.length && isWrapIndent(arr[index - 1]);
130     }
131 
132     /**
133      * Returns: `true` if the top of the indent stack is one of the given token
134      *     types.
135      */
136     bool topIsOneOf(IdType[] types...) const pure nothrow @safe @nogc
137     {
138         if (index == 0)
139             return false;
140         immutable topType = arr[index - 1];
141         foreach (t; types)
142             if (t == topType)
143                 return true;
144         return false;
145     }
146 
147     IdType top() const pure nothrow @property @safe @nogc
148     {
149         return arr[index - 1];
150     }
151 
152     int indentLevel() const pure nothrow @property @safe @nogc
153     {
154         return indentSize();
155     }
156 
157     int length() const pure nothrow @property @safe @nogc
158     {
159         return cast(int) index;
160     }
161 
162     /+void dump()
163     {
164         import std.stdio : stderr;
165         import dparse.lexer : str;
166         import std.algorithm.iteration : map;
167 
168         stderr.writefln("\033[31m%(%s %)\033[0m", arr[0 .. index].map!(a => str(a)));
169     }+/
170 
171 private:
172 
173     size_t index;
174 
175     IdType[256] arr;
176 
177     int indentSize(const size_t k = size_t.max) const pure nothrow @safe @nogc
178     {
179         if (index == 0 || k == 0)
180             return 0;
181         immutable size_t j = k == size_t.max ? index : k;
182         int size = 0;
183         int parenCount;
184         foreach (i; 0 .. j)
185         {
186             immutable int pc = (arr[i] == tok!"!" || arr[i] == tok!"(" || arr[i] == tok!")") ? parenCount + 1
187                 : parenCount;
188             if ((isWrapIndent(arr[i]) || arr[i] == tok!"(") && parenCount > 1)
189             {
190                 parenCount = pc;
191                 continue;
192             }
193             if (i + 1 < index)
194             {
195                 if (arr[i] == tok!"]")
196                     continue;
197                 immutable currentIsNonWrapTemp = !isWrapIndent(arr[i])
198                     && isTempIndent(arr[i]) && arr[i] != tok!")" && arr[i] != tok!"!";
199                 if (arr[i] == tok!"static" && (arr[i + 1] == tok!"if"
200                         || arr[i + 1] == tok!"else") && (i + 2 >= index || arr[i + 2] != tok!"{"))
201                 {
202                     parenCount = pc;
203                     continue;
204                 }
205                 if (currentIsNonWrapTemp && (arr[i + 1] == tok!"switch"
206                         || arr[i + 1] == tok!"{" || arr[i + 1] == tok!")"))
207                 {
208                     parenCount = pc;
209                     continue;
210                 }
211             }
212             else if (parenCount == 0 && arr[i] == tok!"(")
213                 size++;
214             if (arr[i] == tok!"!")
215                 size++;
216             parenCount = pc;
217             size++;
218         }
219         return size;
220     }
221 }
222 
223 unittest
224 {
225     IndentStack stack;
226     stack.push(tok!"{");
227     assert(stack.length == 1);
228     assert(stack.indentLevel == 1);
229     stack.pop();
230     assert(stack.length == 0);
231     assert(stack.indentLevel == 0);
232     stack.push(tok!"if");
233     assert(stack.topIsTemp());
234     stack.popTempIndents();
235     assert(stack.length == 0);
236 }