1 /** 2 * Temple (C) Dylan Knutson, 2013, distributed under the: 3 * Boost Software License - Version 1.0 - August 17th, 2003 4 * 5 * Permission is hereby granted, free of charge, to any person or organization 6 * obtaining a copy of the software and accompanying documentation covered by 7 * this license (the "Software") to use, reproduce, display, distribute, 8 * execute, and transmit the Software, and to prepare derivative works of the 9 * Software, and to permit third-parties to whom the Software is furnished to 10 * do so, all subject to the following: 11 * 12 * The copyright notices in the Software and this entire statement, including 13 * the above license grant, this restriction and the following disclaimer, 14 * must be included in all copies of the Software, in whole or in part, and 15 * all derivative works of the Software, unless such copies or derivative 16 * works are solely in the form of machine-executable object code generated by 17 * a source language processor. 18 */ 19 20 module temple.util; 21 22 private import 23 std.algorithm, 24 std.typecons, 25 std.array, 26 std.uni; 27 28 private import temple.delims; 29 30 package: 31 32 bool validBeforeShort(string str) { 33 // Check that the tail of str is whitespace 34 // before a newline, or nothing. 35 foreach_reverse(dchar chr; str) { 36 if(chr == '\n') { return true; } 37 if(!chr.isWhite()) { return false; } 38 } 39 return true; 40 } 41 42 unittest 43 { 44 static assert(" ".validBeforeShort() == true); 45 static assert(" \t".validBeforeShort() == true); 46 static assert("foo\n".validBeforeShort() == true); 47 static assert("foo\n ".validBeforeShort() == true); 48 static assert("foo\n \t".validBeforeShort() == true); 49 50 static assert("foo \t".validBeforeShort() == false); 51 static assert("foo".validBeforeShort() == false); 52 static assert("\nfoo".validBeforeShort() == false); 53 } 54 55 void munchHeadOf(ref string a, ref string b, size_t amt) { 56 // Transfers amt of b's head onto a's tail 57 a = a ~ b[0..amt]; 58 b = b[amt..$]; 59 } 60 61 unittest 62 { 63 auto a = "123"; 64 auto b = "abc"; 65 a.munchHeadOf(b, 1); 66 assert(a == "123a"); 67 assert(b == "bc"); 68 } 69 unittest 70 { 71 auto a = "123"; 72 auto b = "abc"; 73 a.munchHeadOf(b, b.length); 74 assert(a == "123abc"); 75 assert(b == ""); 76 } 77 78 /// Returns the next matching delimeter in 'delims' found in the haystack, 79 /// or null 80 DelimPos!(D)* nextDelim(D)(string haystack, const(D)[] delims) 81 if(is(D : Delim)) 82 { 83 84 alias Tuple!(Delim, "delim", string, "str") DelimStrPair; 85 86 /// The foreach is there to get around some DMD bugs 87 /// Preferrably, one of the next two lines would be used instead 88 //auto delims_strs = delims.map!(a => new DelimStrPair(a, a.toString()) )().array(); 89 //auto delim_strs = delims_strs.map!(a => a.str)().array(); 90 DelimStrPair[] delims_strs; 91 foreach(delim; delims) 92 { 93 delims_strs ~= DelimStrPair(delim, toString(delim)); 94 } 95 96 // Map delims into their string representations 97 // e.g. OpenDelim.OpenStr => `<%=` 98 string[] delim_strs; 99 foreach(delim; delims) 100 { 101 // Would use ~= here, but CTFE in 2.063 can't handle it 102 delim_strs = delim_strs ~ toString(delim); 103 } 104 105 // Find the first occurance of any of the delimers in the haystack 106 immutable atPos = countUntilAny(haystack, delim_strs); 107 if(atPos == -1) 108 { 109 return null; 110 } 111 112 // Jump to where the delim is on haystack 113 haystack = haystack[atPos .. $]; 114 115 // Make sure that we match the longest of the delimers first, 116 // e.g. `<%=` is matched before `<%` 117 // Think of this as laxy lexing for maximal munch. 118 auto sorted = delims_strs.sort!("a.str.length > b.str.length")(); 119 foreach(s; sorted) 120 { 121 if(startsWith(haystack, s.str)) 122 { 123 return new DelimPos!D(atPos, cast(D) s.delim); 124 } 125 } 126 127 // invariant 128 assert(false, "Internal bug"); 129 } 130 131 unittest 132 { 133 const haystack = Delim.Open.toString(); 134 static assert(*(haystack.nextDelim([Delim.Open])) == DelimPos!Delim(0, Delim.Open)); 135 } 136 unittest 137 { 138 const haystack = "foo"; 139 static assert(haystack.nextDelim([Delim.Open]) is null); 140 } 141 142 /// Returns the location of the first occurance of any of 'subs' found in 143 /// haystack, or -1 if none are found 144 ptrdiff_t countUntilAny(string haystack, string[] subs) { 145 // First, calculate first occurance for all subs 146 auto indexes_of = subs.map!( sub => haystack.countUntil(sub) ); 147 ptrdiff_t min_index = -1; 148 149 // Then find smallest index that isn't -1 150 foreach(index_of; indexes_of) 151 { 152 if(index_of != -1) 153 { 154 if(min_index == -1) 155 { 156 min_index = index_of; 157 } 158 else 159 { 160 min_index = min(min_index, index_of); 161 } 162 } 163 } 164 165 return min_index; 166 } 167 unittest 168 { 169 enum a = "1, 2, 3, 4"; 170 static assert(a.countUntilAny(["1", "2"]) == 0); 171 static assert(a.countUntilAny(["2", "1"]) == 0); 172 static assert(a.countUntilAny(["4", "2"]) == 3); 173 } 174 unittest 175 { 176 enum a = "1, 2, 3, 4"; 177 static assert(a.countUntilAny(["5", "1"]) == 0); 178 static assert(a.countUntilAny(["5", "6"]) == -1); 179 } 180 unittest 181 { 182 enum a = "%>"; 183 static assert(a.countUntilAny(["<%", "<%="]) == -1); 184 } 185 186 string escapeQuotes(string unclean) 187 { 188 unclean = unclean.replace(`"`, `\"`); 189 unclean = unclean.replace(`'`, `\'`); 190 return unclean; 191 } 192 unittest 193 { 194 static assert(escapeQuotes(`"`) == `\"`); 195 static assert(escapeQuotes(`'`) == `\'`); 196 } 197 198 // Internal, inefficiant function for removing the whitespace from 199 // a string (for comparing that templates generate the same output, 200 // ignoring whitespace exactnes) 201 string stripWs(string unclean) { 202 return unclean 203 .filter!(a => !isWhite(a) ) 204 .map!( a => cast(char) a ) 205 .array 206 .idup; 207 } 208 unittest 209 { 210 static assert(stripWs("") == ""); 211 static assert(stripWs(" \t") == ""); 212 static assert(stripWs(" a s d f ") == "asdf"); 213 static assert(stripWs(" a\ns\rd f ") == "asdf"); 214 } 215 216 // Checks if haystack ends with needle, ignoring the whitespace in either 217 // of them 218 bool endsWithIgnoreWhitespace(string haystack, string needle) 219 { 220 haystack = haystack.stripWs; 221 needle = needle.stripWs; 222 223 return haystack.endsWith(needle); 224 } 225 226 unittest 227 { 228 static assert(endsWithIgnoreWhitespace(") { ", "){")); 229 static assert(!endsWithIgnoreWhitespace(") {}", "){")); 230 } 231 232 bool startsWithBlockClose(string haystack) 233 { 234 haystack = haystack.stripWs; 235 236 // something that looks like }<something>); passes this 237 if(haystack.startsWith("}") && haystack.canFind(");")) return true; 238 return false; 239 } 240 241 unittest 242 { 243 static assert(startsWithBlockClose(`}, 10);`)); 244 static assert(startsWithBlockClose(`});`)); 245 static assert(startsWithBlockClose(`}, "foo");`)); 246 static assert(startsWithBlockClose(`}); auto a = "foo";`)); 247 248 static assert(!startsWithBlockClose(`if() {}`)); 249 static assert(!startsWithBlockClose(`};`)); 250 } 251 252 bool isBlockStart(string haystack) 253 { 254 return haystack.endsWithIgnoreWhitespace("){"); 255 } 256 257 bool isBlockEnd(string haystack) 258 { 259 return haystack.startsWithBlockClose(); 260 }