1 /++ 2 Some misc stuff used by the compiler 3 +/ 4 module qscript.compiler.misc; 5 6 import utils.misc; 7 import utils.lists; 8 9 import std.range; 10 import std.conv : to; 11 12 /// An array containing all chars that an identifier can contain 13 package const char[] IDENT_CHARS = iota('a', 'z'+1).array~iota('A', 'Z'+1).array~iota('0', '9'+1).array~[cast(int)'_']; 14 /// An array containing all keywords 15 package const string[] KEYWORDS = ["function", "return", "if", "else", "while", "for", "do", "void", "int", "string", "double"]; 16 /// data types 17 package const string[] DATA_TYPES = ["void", "int", "double", "string"]; 18 /// An array containing another array conatining double-operand operators 19 package const string[] OPERATORS = ["/", "*", "+", "-", "%", "~", "<", ">", ">=", "<=", "==", "=", "&&", "||"]; 20 /// single-operand operators 21 package const string[] SOPERATORS = ["!", "@"]; 22 /// An array containing all bool-operators (operators that return true/false) 23 package const string[] BOOL_OPERATORS = ["<", ">", ">=", "<=", "==", "&&", "||"]; 24 /// Inbuilt QScript functions (like `length(void[])`) 25 package Function[] INBUILT_FUNCTIONS = [ 26 /// length(@void[], int) 27 Function("length", DataType(DataType.Type.Void), [DataType(DataType.Type.Void, 1, true), DataType(DataType.Type.Integer)]), 28 /// length(void[]) 29 Function("length", DataType(DataType.Type.Integer), [DataType(DataType.Type.Void, 1)]), 30 /// length (string) 31 Function("length", DataType(DataType.Type.Integer), [DataType(DataType.Type.String)]), 32 33 /// toInt(string) 34 Function("toInt", DataType(DataType.Type.Integer), [DataType(DataType.Type.String)]), 35 /// toInt(double) 36 Function("toInt", DataType(DataType.Type.Integer), [DataType(DataType.Type.Double)]), 37 /// toDouble(string) 38 Function("toDouble", DataType(DataType.Type.Double), [DataType(DataType.Type.String)]), 39 /// toDouble(int) 40 Function("toDouble", DataType(DataType.Type.Double), [DataType(DataType.Type.Integer)]), 41 /// toString(int) 42 Function("toStr", DataType(DataType.Type.String), [DataType(DataType.Type.Integer)]), 43 /// toString(double) 44 Function("toStr", DataType(DataType.Type.String), [DataType(DataType.Type.Double)]) 45 ]; 46 47 /// Used by compiler's functions to return error 48 public struct CompileError{ 49 string msg; /// The error stored in a string 50 uinteger lineno; /// The line number on which the error is 51 this(uinteger lineNumber, string errorMessage){ 52 lineno = lineNumber; 53 msg = errorMessage; 54 } 55 } 56 57 /// To store information about a function 58 public struct Function{ 59 /// the name of the function 60 string name; 61 /// the data type of the value returned by this function 62 DataType returnType; 63 /// stores the data type of the arguments received by this function 64 private DataType[] _argTypes; 65 /// the data type of the arguments received by this function 66 /// 67 /// if an argType is defined as void, with array dimensions=0, it means accept any type. 68 /// if an argType is defined as void with array dimensions>0, it means array of any type of that dimensions 69 @property ref DataType[] argTypes(){ 70 return _argTypes; 71 } 72 /// the data type of the arguments received by this function 73 @property ref DataType[] argTypes(DataType[] newArray){ 74 return _argTypes = newArray.dup; 75 } 76 /// constructor 77 this (string functionName, DataType functionReturnType, DataType[] functionArgTypes){ 78 name = functionName; 79 returnType = functionReturnType; 80 _argTypes = functionArgTypes.dup; 81 } 82 } 83 84 /// used to store data types for data at compile time 85 public struct DataType{ 86 /// enum defining all data types 87 public enum Type{ 88 Void, 89 String, 90 Integer, 91 Double 92 } 93 /// the actual data type 94 Type type = DataType.Type.Void; 95 /// stores if it's an array. If type is `int`, it will be 0, if `int[]` it will be 1, if `int[][]`, then 2 ... 96 uinteger arrayDimensionCount = 0; 97 /// stores if it's a reference to a type 98 bool isRef = false; 99 /// returns true if it's an array 100 @property bool isArray(){ 101 if (arrayDimensionCount > 0){ 102 return true; 103 } 104 return false; 105 } 106 /// constructor 107 /// 108 /// dataType is the type to store 109 /// arrayDimension is the number of nested arrays 110 /// isRef is whether the type is a reference to the actual type 111 this (DataType.Type dataType, uinteger arrayDimension = 0, bool isReference = false){ 112 type = dataType; 113 arrayDimensionCount = arrayDimension; 114 isRef = isReference; 115 } 116 117 /// constructor 118 /// 119 /// `sType` is the type in string form 120 this (string sType){ 121 fromString(sType); 122 } 123 /// constructor 124 /// 125 /// `data` is the data to infer type from 126 this (Token[] data){ 127 fromData(data); 128 } 129 /// converts this to a byte code style data type, which is a string 130 string toByteCode(){ 131 auto TYPE_CODE = [ 132 DataType.Type.Void : '0', 133 DataType.Type.String : '1', 134 DataType.Type.Integer : '2', 135 DataType.Type.Double : '3' 136 ]; 137 return TYPE_CODE[type] ~ (isRef ? "1" : "0") ~ to!string(arrayDimensionCount); 138 } 139 /// reads DataType from a byte code style string 140 /// 141 /// Returns: true if successful, false if the string was invalid 142 bool fromByteCode(string s){ 143 auto TYPE_CODE = [ 144 '0' : DataType.Type.Void, 145 '1' : DataType.Type.String, 146 '2' : DataType.Type.Integer, 147 '3' : DataType.Type.Double 148 ]; 149 if (s.length < 3 || !isNum(s, false) || s[0] !in TYPE_CODE || !['0','1'].hasElement(s[1])) 150 return false; 151 type = TYPE_CODE[s[0]]; 152 if (s[1] == '1') 153 isRef = true; 154 else 155 isRef = false; 156 arrayDimensionCount = to!uinteger(s[2 .. s.length]); 157 return true; 158 } 159 /// reads DataType from a string, in case of failure or bad format in string, throws Exception 160 void fromString(string s){ 161 isRef = false; 162 string sType = null; 163 uinteger indexCount = 0; 164 // check if it's a ref 165 if (s.length > 0 && s[0] == '@'){ 166 isRef = true; 167 s = s.dup; 168 s = s[1 .. s.length]; 169 } 170 // read the type 171 for (uinteger i = 0, lastInd = s.length-1; i < s.length; i ++){ 172 if (s[i] == '['){ 173 sType = s[0 .. i]; 174 break; 175 }else if (i == lastInd){ 176 sType = s; 177 break; 178 } 179 } 180 // now read the index 181 for (uinteger i = sType.length; i < s.length; i ++){ 182 if (s[i] == '['){ 183 // make sure next char is a ']' 184 i ++; 185 if (s[i] != ']'){ 186 throw new Exception("invalid data type format"); 187 } 188 indexCount ++; 189 }else{ 190 throw new Exception("invalid data type"); 191 } 192 } 193 // now check if the type was ok or not 194 if (sType == "void"){ 195 type = DataType.Type.Void; 196 }else if (sType == "string"){ 197 type = DataType.Type.String; 198 }else if (sType == "int"){ 199 type = DataType.Type.Integer; 200 }else if (sType == "double"){ 201 type = DataType.Type.Double; 202 }else{ 203 throw new Exception("invalid data type"); 204 } 205 arrayDimensionCount = indexCount; 206 } 207 208 /// identifies the data type from the actual data 209 /// keep in mind, this won't be able to identify if the data type is a reference or not 210 /// 211 /// throws Exception on failure 212 void fromData(Token[] data){ 213 /// identifies type from data 214 static DataType.Type identifyType(Token data){ 215 if (data.type == Token.Type.String){ 216 return DataType.Type.String; 217 }else if (data.type == Token.Type.Integer){ 218 return DataType.Type.Integer; 219 }else if (data.type == Token.Type.Double){ 220 return DataType.Type.Double; 221 }else{ 222 throw new Exception("failed to read data type"); 223 } 224 } 225 // keeps count of number of "instances" of this function currently being executed 226 static uinteger callCount = 0; 227 callCount ++; 228 229 if (callCount == 1){ 230 this.arrayDimensionCount = 0; 231 this.type = DataType.Type.Void; 232 } 233 // check if is an array 234 if (data.length > 1 && 235 data[0].type == Token.Type.IndexBracketOpen && data[data.length-1].type == Token.Type.IndexBracketClose){ 236 // is an array 237 this.arrayDimensionCount ++; 238 // if elements are arrays, do recursion, else, just identify types 239 Token[][] elements = splitArray(data); 240 if (elements.length == 0){ 241 this.type = DataType.Type.Void; 242 }else{ 243 // determine the type using recursion 244 // stores the arrayDimensionCount till here 245 uinteger thisNestCount = this.arrayDimensionCount; 246 // stores the nestCount for the preious element, -1 if no previous element 247 integer prevNestCount = -1; 248 // stores the data type of the last element, void if no last element 249 DataType.Type prevType = DataType.Type.Void; 250 // now do recursion, and make sure all types come out same 251 foreach (element; elements){ 252 fromData(element); 253 // now make sure the nestCount came out same 254 if (prevNestCount != -1){ 255 if (prevNestCount != this.arrayDimensionCount){ 256 throw new Exception("inconsistent data types in array elements"); 257 } 258 }else{ 259 // set new nestCount 260 prevNestCount = this.arrayDimensionCount; 261 } 262 // now to make sure type came out same 263 if (prevType != DataType.Type.Void){ 264 if (this.type != prevType){ 265 throw new Exception("inconsistent data types in array elements"); 266 } 267 }else{ 268 prevType = this.type; 269 } 270 // re-set the nestCount for the next element 271 this.arrayDimensionCount = thisNestCount; 272 } 273 // now set the nestCount 274 this.arrayDimensionCount = prevNestCount; 275 } 276 }else if (data.length == 0){ 277 this.type = DataType.Type.Void; 278 }else{ 279 // then it must be only one token, if is zero, then it's void 280 assert(data.length == 1, "non-array data must be only one token in length"); 281 // now check the type, and set it 282 this.type = identifyType(data[0]); 283 } 284 callCount --; 285 } 286 287 /// converts this DataType to string 288 string toString(){ 289 char[] r; 290 if (type == DataType.Type.Void){ 291 r = cast(char[]) "void"; 292 }else if (type == DataType.Type.Double){ 293 r = cast(char[]) "double"; 294 }else if (type == DataType.Type.Integer){ 295 r = cast(char[]) "int"; 296 }else if (type == DataType.Type.String){ 297 r = cast(char[]) "string"; 298 }else{ 299 throw new Exception("invalid type stored: "~to!string(type)); 300 } 301 if (arrayDimensionCount > 0){ 302 uinteger i = r.length; 303 r.length += arrayDimensionCount * 2; 304 for (; i < r.length; i += 2){ 305 r[i .. i+2] = "[]"; 306 } 307 } 308 return cast(string) (isRef ? '@'~r : r); 309 } 310 } 311 /// 312 unittest{ 313 assert(DataType("int") == DataType(DataType.Type.Integer, 0)); 314 assert(DataType("string[]") == DataType(DataType.Type.String, 1)); 315 assert(DataType("double[][]") == DataType(DataType.Type.Double, 2)); 316 assert(DataType("void") == DataType(DataType.Type.Void, 0)); 317 // unittests for `fromData` 318 import qscript.compiler.tokengen : stringToTokens; 319 DataType dType; 320 dType.fromData(["\"bla bla\""].stringToTokens); 321 assert(dType == DataType("string")); 322 dType.fromData(["20"].stringToTokens); 323 assert(dType == DataType("int")); 324 dType.fromData(["2.5"].stringToTokens); 325 assert(dType == DataType("double")); 326 dType.fromData(["[", "\"bla\"", ",", "\"bla\"", "]"].stringToTokens); 327 assert(dType == DataType("string[]")); 328 dType.fromData(["[", "[", "25.0", ",", "2.5", "]", ",", "[", "15.0", ",", "25.0", "]", "]"].stringToTokens); 329 assert(dType == DataType("double[][]")); 330 } 331 332 /// splits an array in tokens format to it's elements 333 /// 334 /// For example, splitArray("[a, b, c]") will return ["a", "b", "c"] 335 package Token[][] splitArray(Token[] array){ 336 assert(array[0].type == Token.Type.IndexBracketOpen && 337 array[array.length - 1].type == Token.Type.IndexBracketClose, "not a valid array"); 338 LinkedList!(Token[]) elements = new LinkedList!(Token[]); 339 for (uinteger i = 1, readFrom = i; i < array.length; i ++){ 340 // skip any other brackets 341 if (array[i].type == Token.Type.BlockStart || array[i].type == Token.Type.IndexBracketOpen || 342 array[i].type == Token.Type.ParanthesesOpen){ 343 i = tokenBracketPos!(true)(array, i); 344 continue; 345 } 346 // check if comma is here 347 if (array[i].type == Token.Type.Comma || array[i].type == Token.Type.IndexBracketClose){ 348 if ((readFrom > i || readFrom == i) && array[i].type == Token.Type.Comma){ 349 throw new Exception("syntax error"); 350 } 351 elements.append(array[readFrom .. i]); 352 readFrom = i + 1; 353 } 354 } 355 Token[][] r = elements.toArray; 356 .destroy(elements); 357 return r; 358 } 359 360 /// Returns the index of the quotation mark that ends a string 361 /// 362 /// Returns -1 if not found 363 package integer strEnd(string s, uinteger i){ 364 for (i++;i<s.length;i++){ 365 if (s[i]=='\\'){ 366 i++; 367 continue; 368 }else if (s[i]=='"'){ 369 break; 370 } 371 } 372 if (i==s.length){i=-1;} 373 return i; 374 } 375 376 /// decodes a string. i.e, converts \t to tab, \" to ", etc 377 /// The string must not be surrounded by quoation marks 378 /// 379 /// throws Exception on error 380 string decodeString(string s){ 381 string r; 382 for (uinteger i = 0; i < s.length; i ++){ 383 if (s[i] == '\\'){ 384 // read the next char 385 if (i == s.length-1){ 386 throw new Exception("unexpected end of string"); 387 } 388 char nextChar = s[i+1]; 389 if (nextChar == '"'){ 390 r ~= '"'; 391 }else if (nextChar == 'n'){ 392 r ~= '\n'; 393 }else if (nextChar == 't'){ 394 r ~= '\t'; 395 }else if (nextChar == '\\'){ 396 r ~= '\\'; 397 }else{ 398 throw new Exception("\\"~nextChar~" is not an available character"); 399 } 400 i ++; 401 continue; 402 } 403 r ~= s[i]; 404 } 405 return r; 406 } 407 408 /// encodes a string. i.e converts characters like tab, newline, to \t, \n ... 409 /// The string must not be enclosed in quotation marks 410 /// 411 /// throws Exception on error 412 string encodeString(string s){ 413 string r; 414 foreach (c; s){ 415 if (c == '\\'){ 416 r ~= "\\\\"; 417 }else if (c == '"'){ 418 r ~= "\\\""; 419 }else if (c == '\n'){ 420 r ~= "\\n"; 421 }else if (c == '\t'){ 422 r ~= "\\t"; 423 }else{ 424 r ~= c; 425 } 426 } 427 return r; 428 } 429 430 /// converts a function name and it's arguments to a byte code style function name 431 /// 432 /// Arguments: 433 /// `name` is the function name 434 /// `argTypes` is the array of it's arguments' Data Types 435 /// 436 /// Returns: the byte code style function name 437 string encodeFunctionName (string name, DataType[] argTypes){ 438 string r = name ~ '/'; 439 foreach (argType; argTypes) 440 r = r ~ argType.toByteCode~ '/'; 441 return r; 442 } 443 /// 444 unittest{ 445 assert ("abcd".encodeFunctionName ([DataType(DataType.Type.Double,14),DataType(DataType.Type.Void)]) == 446 "abcd/3014/000/" 447 ); 448 } 449 450 /// reads a byte code style function name into a function name and argument types 451 /// 452 /// Arguments: 453 /// `encodedName` is the encoded function name 454 /// `name` is the variable to put the decoded name in 455 /// `argTypes` is the array to put arguments' data types in 456 /// 457 /// Returns: true if the name was in a correct format and was read correctly 458 /// false if there was an error reading it 459 bool decodeFunctionName (string encodedName, ref string name, ref DataType[] argTypes){ 460 // separate it from all the slashes 461 string[] parts; 462 for (uinteger i = 0, readFrom = 0; i < encodedName.length; i ++){ 463 if (encodedName[i] == '/'){ 464 parts ~= encodedName[readFrom .. i]; 465 readFrom = i + 1; 466 if (parts[parts.length -1].length == 0) 467 return false; 468 continue; 469 } 470 } 471 if (parts.length == 0) 472 return false; 473 name = parts[0]; 474 parts = parts[1 .. parts.length]; 475 argTypes.length = parts.length; 476 foreach (i, part; parts){ 477 if (!argTypes[i].fromByteCode(part)) 478 return false; 479 } 480 return true; 481 } 482 /// 483 unittest{ 484 string name; 485 DataType[] types; 486 "abcd/0014/102/203/308/".decodeFunctionName(name, types); 487 assert (name == "abcd"); 488 assert (types == [DataType(DataType.Type.Void, 14), 489 DataType(DataType.Type.String, 2), 490 DataType(DataType.Type.Integer, 3), 491 DataType(DataType.Type.Double, 8) 492 ]); 493 } 494 495 /// matches argument types with defined argument types. Used by ASTGen and compiler.d. 496 /// 497 /// * `void` will match true against all types (arrays, and even references) 498 /// * `@void` will match true against only references of any type 499 /// * `@void[]` will match true against only references of any type of array 500 /// * `void[]` will match true against only any type of array (even references) 501 /// 502 /// returns: true if match successful, else, false 503 bool matchArguments(DataType[] definedTypes, DataType[] argTypes){ 504 if (argTypes.length != definedTypes.length){ 505 return false; 506 }else{ 507 for (uinteger i = 0; i < argTypes.length; i ++){ 508 if (definedTypes[i].isRef && argTypes[i].isRef != true){ 509 return false; 510 } 511 // check the array dimension 512 if (definedTypes[i].arrayDimensionCount > 0 && argTypes[i].arrayDimensionCount == 0){ 513 return false; 514 } 515 if (definedTypes[i].type == DataType.Type.Void){ 516 // skip all checks 517 continue; 518 }else if (argTypes[i].type != definedTypes[i].type){ 519 return false; 520 } 521 } 522 return true; 523 } 524 } 525 526 /// checks if a function can be called with a set of arguments. 527 /// 528 /// fName is the byte-code style function name (see `encodeFunctionName`). 529 /// argTypes is the data types of the arguments 530 /// 531 /// Returns: true if it can be called, false if not, or if the fName was incorrect 532 bool canCallFunction(string fName, DataType[] argTypes){ 533 // decode to get the right name & args 534 DataType[] expectedArgTypes; 535 { 536 string name; 537 if (!decodeFunctionName(fName, name, expectedArgTypes)){ 538 return false; 539 } 540 } 541 return matchArguments(expectedArgTypes, argTypes); 542 } 543 544 /// Each token is stored as a `Token` with the type and the actual token 545 package struct Token{ 546 /// Specifies type of token 547 /// 548 /// used only in `compiler.tokengen` 549 enum Type{ 550 String,/// That the token is: `"SOME STRING"` 551 Integer,/// That the token an int 552 Double, /// That the token is a double (floating point) value 553 Identifier,/// That the token is an identifier. i.e token is a variable name or a function name. For a token to be marked as Identifier, it doesn't need to be defined in `new()` 554 DataType, /// the token is a data type 555 MemberSelector, /// a member selector operator 556 AssignmentOperator, /// and assignmentOperator 557 Operator,/// That the token is an operator, like `+`, `==` etc 558 Keyword,/// A `function` or `var` ... 559 Comma,/// That its a comma: `,` 560 StatementEnd,/// A semicolon 561 ParanthesesOpen,/// `(` 562 ParanthesesClose,/// `)` 563 IndexBracketOpen,/// `[` 564 IndexBracketClose,///`]` 565 BlockStart,///`{` 566 BlockEnd,///`}` 567 } 568 Type type;/// type of token 569 string token;/// token 570 this(Type tType, string tToken){ 571 type = tType; 572 token = tToken; 573 } 574 } 575 /// To store Tokens with Types where the line number of each token is required 576 package struct TokenList{ 577 Token[] tokens; /// Stores the tokens 578 uinteger[] tokenPerLine; /// Stores the number of tokens in each line 579 /// Returns the line number a token is in by usin the index of the token in `tokens` 580 /// 581 /// The returned value is the line-number, not index, it starts from 1, not zero 582 uinteger getTokenLine(uinteger tokenIndex){ 583 uinteger i = 0, chars = 0; 584 tokenIndex ++; 585 for (; chars < tokenIndex && i < tokenPerLine.length; i ++){ 586 chars += tokenPerLine[i]; 587 } 588 return i; 589 } 590 /// reads tokens into a string 591 static string toString(Token[] t){ 592 char[] r; 593 // set length 594 uinteger length = 0; 595 for (uinteger i = 0; i < t.length; i ++){ 596 length += t[i].token.length; 597 } 598 r.length = length; 599 // read em all into r; 600 for (uinteger i = 0, writeTo = 0; i < t.length; i ++){ 601 r[writeTo .. writeTo + t[i].token.length] = t[i].token; 602 writeTo += t[i].token.length; 603 } 604 return cast(string)r; 605 } 606 } 607 608 /// Checks if the brackets in a tokenlist are in correct order, and are closed 609 /// 610 /// In case not, returns false, and appends error to `errorLog` 611 package bool checkBrackets(TokenList tokens, LinkedList!CompileError errors){ 612 enum BracketType{ 613 Round, 614 Square, 615 Block 616 } 617 BracketType[Token.Type] brackOpenIdent = [ 618 Token.Type.ParanthesesOpen: BracketType.Round, 619 Token.Type.IndexBracketOpen: BracketType.Square, 620 Token.Type.BlockStart: BracketType.Block 621 ]; 622 BracketType[Token.Type] brackCloseIdent = [ 623 Token.Type.ParanthesesClose: BracketType.Round, 624 Token.Type.IndexBracketClose: BracketType.Square, 625 Token.Type.BlockEnd:BracketType.Block 626 ]; 627 Stack!BracketType bracks = new Stack!BracketType; 628 Stack!uinteger bracksStartIndex = new Stack!uinteger; 629 BracketType curType; 630 bool r = true; 631 for (uinteger lastInd = tokens.tokens.length-1, i = 0; i<=lastInd; i++){ 632 if (tokens.tokens[i].type in brackOpenIdent){ 633 bracks.push(brackOpenIdent[tokens.tokens[i].type]); 634 bracksStartIndex.push(i); 635 }else if (tokens.tokens[i].type in brackCloseIdent){ 636 bracksStartIndex.pop(); 637 if (bracks.pop != brackCloseIdent[tokens.tokens[i].type]){ 638 errors.append(CompileError(tokens.getTokenLine(i), 639 "brackets order is wrong; first opened must be last closed")); 640 r = false; 641 break; 642 } 643 }else if (i == lastInd && bracks.count > 0){ 644 // no bracket ending 645 i = -2; 646 errors.append(CompileError(tokens.getTokenLine(bracksStartIndex.pop), "bracket not closed")); 647 r = false; 648 break; 649 } 650 } 651 652 .destroy(bracks); 653 .destroy(bracksStartIndex); 654 return r; 655 } 656 657 /// Returns index of closing/openinig bracket of the provided bracket 658 /// 659 /// `forward` if true, then the search is in forward direction, i.e, the closing bracket is searched for 660 /// `tokens` is the array of tokens to search in 661 /// `index` is the index of the opposite bracket 662 /// 663 /// It only works correctly if the brackets are in correct order, and the closing bracket is present 664 /// so, before calling this, `compiler.misc.checkBrackets` should be called 665 package uinteger tokenBracketPos(bool forward=true)(Token[] tokens, uinteger index){ 666 Token.Type[] closingBrackets = [ 667 Token.Type.BlockEnd, 668 Token.Type.IndexBracketClose, 669 Token.Type.ParanthesesClose 670 ]; 671 Token.Type[] openingBrackets = [ 672 Token.Type.BlockStart, 673 Token.Type.IndexBracketOpen, 674 Token.Type.ParanthesesOpen 675 ]; 676 uinteger count; // stores how many closing/opening brackets before we reach the desired one 677 uinteger i = index; 678 for (uinteger lastInd = (forward ? tokens.length : 0); i != lastInd; (forward ? i ++: i --)){ 679 if ((forward ? openingBrackets : closingBrackets).hasElement(tokens[i].type)){ 680 count ++; 681 }else if ((forward ? closingBrackets : openingBrackets).hasElement(tokens[i].type)){ 682 count --; 683 } 684 if (count == 0){ 685 break; 686 } 687 } 688 return i; 689 } 690 /// 691 unittest{ 692 Token[] tokens; 693 tokens = [ 694 Token(Token.Type.Comma, ","), Token(Token.Type.BlockStart,"{"), Token(Token.Type.Comma,","), 695 Token(Token.Type.IndexBracketOpen,"["),Token(Token.Type.IndexBracketClose,"]"),Token(Token.Type.BlockEnd,"}") 696 ]; 697 assert(tokens.bracketPos!true(1) == 5); 698 assert(tokens.bracketPos!false(5) == 1); 699 assert(tokens.bracketPos!true(3) == 4); 700 assert(tokens.bracketPos!false(4) == 3); 701 } 702 703 /// removes "extra" whitespace from a string. i.e, if there are more than 1 consecutive spaces/tabs, one is removed 704 /// 705 /// `line` is the string to remove whitespace from 706 /// `commentStart` is the character that marks the start of a comment, if ==0, then comments are not not considered 707 package string removeWhitespace(string line, char commentStart=0){ 708 bool prevWasWhitespace = true; 709 string r; 710 foreach (c; line){ 711 if (prevWasWhitespace && (c == ' ' || c == '\t')){ 712 // do not add this one then 713 continue; 714 }else if (c != 0 && c == commentStart){ 715 break; 716 }else{ 717 prevWasWhitespace = false; 718 r ~= c; 719 } 720 } 721 return r; 722 }