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 }