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 }