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 }