1 /++
2 Functions to check if a script is valid (or certain nodes) by its generated AST
3 +/
4 module qscript.compiler.astcheck;
5 
6 import qscript.compiler.compiler;
7 import qscript.compiler.ast;
8 
9 import utils.misc;
10 import utils.lists;
11 
12 import std.conv : to;
13 
14 /// To temporarily store variables
15 private class VarStore{
16 private:
17 	/// variables exported by this library. index is ID
18 	List!Variable _vars;
19 	/// number of variables in different scopes
20 	List!uinteger _scopeVarCount;
21 	/// maximum number of variables declared in and after a specific scope (inside function)
22 	uinteger _scopeMaxVars;
23 	/// the scope to count max variables from
24 	uinteger _scopeIndexVarCount;
25 public:
26 	/// constructor
27 	this(){
28 		_scopeVarCount = new List!uinteger;
29 		_vars = new List!Variable;
30 		_scopeVarCount.append(0);
31 		_scopeMaxVars = 0;
32 		_scopeIndexVarCount = -1;
33 	}
34 	~this(){
35 		.destroy(_vars);
36 		.destroy(_scopeVarCount);
37 	}
38 	/// clears
39 	void clear(){
40 		_vars.clear();
41 		_scopeVarCount.clear;
42 		_scopeVarCount.append(0);
43 		_scopeMaxVars = 0;
44 		_scopeIndexVarCount = -1;
45 	}
46 	/// removes a number of variables
47 	void removeVars(uinteger count){
48 		if (count > _vars.length)
49 			_vars.clear;
50 		else
51 			_vars.removeLast(count);
52 	}
53 	/// increases scope
54 	void scopeIncrease(){
55 		_scopeVarCount.append(0);
56 	}
57 	/// decrease scope
58 	void scopeDecrease(){
59 		// count how many variables going away
60 		uinteger varCount = 0;
61 		if (_scopeIndexVarCount != -1){
62 			foreach (i; _scopeIndexVarCount .. _scopeVarCount.length)
63 				varCount += _scopeVarCount.read(i);
64 			if (varCount > _scopeMaxVars)
65 				_scopeMaxVars = varCount;
66 		}
67 		_vars.removeLast(_scopeVarCount.readLast);
68 		_scopeVarCount.removeLast();
69 		if (_scopeVarCount.length == 0)
70 			_scopeVarCount.append(0);
71 	}
72 	/// Start counting how many variables are going to be available in current scope
73 	void scopeVarCountStart(){
74 		_scopeIndexVarCount = cast(integer)_scopeVarCount.length-1;
75 		_scopeMaxVars = 0;
76 	}
77 	/// how many variables are in a scope (and in scopes within this scope and in...). Use `scopeVarCountStart` to specify scope
78 	/// 
79 	/// Call this AFTER `decreaseScope`
80 	/// 
81 	/// Returns: number of variables in scope
82 	@property uinteger scopeVarCount(){
83 		_scopeIndexVarCount = -1;
84 		return _scopeMaxVars;
85 	}
86 	/// variables exported by this library. index is ID
87 	@property Variable[] vars(){
88 		return _vars.toArray;
89 	}
90 	/// Returns: variable ID, or -1 if doesnt exist
91 	integer hasVar(string name, ref DataType type){
92 		_vars.seek = 0;
93 		while (_vars.seek < _vars.length){
94 			immutable Variable var = _vars.read;
95 			if (var.name == name){
96 				type = var.type;
97 				return _vars.seek - 1;
98 			}
99 		}
100 		return -1;
101 	}
102 	/// Returns: true if variable exists
103 	bool hasVar(string name){
104 		DataType dummy;
105 		return this.hasVar(name, dummy) > 0;
106 	}
107 	/// Adds a new variable.
108 	/// 
109 	/// Returns: Variable ID, or -1 if it already exists
110 	integer addVar(Variable var){
111 		if (this.hasVar(var.name))
112 			return -1;
113 		_vars.append(var);
114 		_scopeVarCount.set(cast(integer)_scopeVarCount.length-1, _scopeVarCount.readLast+1);
115 		return cast(integer)_vars.length-1;
116 	}
117 }
118 
119 /// Contains functions to check ASTs for errors  
120 /// One instance of this class can be used to check for errors in a script's AST.
121 class ASTCheck{
122 private:
123 	/// stores the libraries available. Index is the library ID.
124 	Library[] _libraries;
125 	/// stores whether a library was imported. Index is library id
126 	bool[integer] _isImported;
127 	/// stores all declarations of this script, public and private. But not variables, those keep changes as going from function to function.
128 	/// _vars store them
129 	Library _this;
130 	/// variables that are curerntly in scope
131 	VarStore _vars;
132 	/// stores this script's public declarations, this is what is exported at end
133 	Library _exports;
134 	/// stores all the errors that are there in the AST being checked
135 	LinkedList!CompileError compileErrors;
136 	/// stores expected return type of currently-being-checked function
137 	DataType functionReturnType;
138 	/// registers a new  var in current scope
139 	/// 
140 	/// Returns: false if it was already registered or global variable with same name exists,  
141 	/// true if it was successful
142 	bool addVar(string name, DataType type, bool isGlobal = false){
143 		if (_vars.hasVar(name))
144 			return false;
145 		if (isGlobal)
146 			_exports.addVar(Variable(name, type));
147 		_vars.addVar(Variable(name, type));
148 		return true;
149 	}
150 	/// Returns: true if a var exists. False if not.
151 	/// also sets the variable data type to `type` and id to `id`, and library id to `libraryId`, and 
152 	/// if it is global, sets `isGlobal` to `true`
153 	bool getVar(string name, ref DataType type, ref integer id, ref integer libraryId, ref bool isGlobal){
154 		id = _vars.hasVar(name, type);
155 		if (id > -1){
156 			isGlobal = id < _exports.vars.length;
157 			libraryId = -1;
158 			return true;
159 		}
160 		foreach (libId, library; _libraries){
161 			if (!isImported(libId))
162 				continue;
163 			id = library.hasVar(name, type);
164 			if (id > -1){
165 				isGlobal = true;
166 				libraryId = libId;
167 				return true;
168 			}
169 		}
170 		return false;
171 	}
172 	/// Returns: variable ID for a variable with a name. Only use when you know the variable is local
173 	uinteger getVarID(string name){
174 		DataType type;
175 		integer id;
176 		integer libraryId;
177 		bool isGlobal;
178 		getVar(name, type, id, libraryId, isGlobal);
179 		return id;
180 	}
181 	/// Returns: true if a variable with a name is found
182 	bool varExists(string name){
183 		DataType type;
184 		integer id;
185 		integer libraryId;
186 		bool isGlobal;
187 		return getVar(name, type, id, libraryId, isGlobal);
188 	}
189 	/// Returns: true if a function exists, false if not. Sets function return type to `type`,
190 	/// id to `id`, and library id to `libraryId`
191 	bool getFunction(string name, DataType[] argTypes, ref DataType type, ref integer id,
192 		ref integer libraryId){
193 		id = _this.hasFunction(name, argTypes, type);
194 		if (id > -1){
195 			libraryId = -1;
196 			return true;
197 		}
198 		foreach (libId, lib; _libraries){
199 			if (!isImported(libId))
200 				continue;
201 			id = lib.hasFunction(name, argTypes, type);
202 			if (id > -1){
203 				libraryId = libId;
204 				return true;
205 			}
206 		}
207 		return false;
208 	}
209 	/// Returns: true if a struct exists, false if not. Sets struct data to `structData`
210 	bool getStruct(string name, ref Struct structData){
211 		if (_this.hasStruct(name, structData))
212 			return true;
213 		foreach (integer libId, lib; _libraries){
214 			if (!isImported(libId))
215 				continue;
216 			if (lib.hasStruct(name, structData))
217 				return true;
218 		}
219 		return false;
220 	}
221 	/// Returns: true if an enum exists, false if not. Sets enum data to `enumData`
222 	bool getEnum(string name, ref Enum enumData){
223 		if (_this.hasEnum(name, enumData))
224 			return true;
225 		foreach (integer libId, library; _libraries){
226 			if (!isImported(libId))
227 				continue;
228 			if (library.hasEnum(name, enumData))
229 				return true;
230 		}
231 		return false;
232 	}
233 	/// reads all imports from ScriptNode
234 	void readImports(ScriptNode node){
235 		_isImported[-1] = true; // first mark -1 as imported, coz thats used for local stuff
236 		foreach (importName; node.imports){
237 			integer index = -1;
238 			foreach (i, library; _libraries){
239 				if (library.name == importName){
240 					index = i;
241 					break;
242 				}
243 			}
244 			if (index == -1)
245 				compileErrors.append(CompileError(0, "cannot import "~importName~", does not exist"));
246 			_isImported[index] = true;
247 		}
248 	}
249 	/// reads all FunctionNode from ScriptNode
250 	/// 
251 	/// any error is appended to compileErrors
252 	void readFunctions(ScriptNode node){
253 		/// first check for conflicts, also append public functions
254 		bool thisFunctionDeclared = false; // if the `this()` function has been declared
255 		foreach (funcId; 0 .. node.functions.length){
256 			FunctionNode funcA = node.functions[funcId];
257 			if (funcA.name == "this"){
258 				if (thisFunctionDeclared)
259 					compileErrors.append(CompileError(funcA.lineno, "`this` function defined multiple times"));
260 				else
261 					thisFunctionDeclared = true;
262 			}
263 			foreach (i; 0 .. funcId){
264 				FunctionNode funcB = node.functions[i];
265 				if (funcA.name == funcB.name && funcA.argTypes == funcB.argTypes)
266 					compileErrors.append(CompileError(funcB.lineno,
267 						"functions with same name must have different argument types"));
268 			}
269 			// if return type is an enum, change it to int
270 			if (funcA.returnType.isCustom){
271 				Enum dummyEnum;
272 				Struct dummyStruct;
273 				if (getEnum(funcA.returnType.typeName, dummyEnum))
274 					node.functions[funcId].returnType.type = DataType.Type.Int;
275 				else if (!getStruct(funcA.returnType.typeName, dummyStruct)){
276 					compileErrors.append(CompileError(funcA.lineno, "invalid data type "~funcA.returnType.typeName));
277 					continue;
278 				}
279 			}
280 			// append if public
281 			if (funcA.visibility == Visibility.Public){
282 				_this.addFunction(Function(funcA.name, funcA.returnType, funcA.argTypes));
283 				_exports.addFunction(Function(funcA.name, funcA.returnType, funcA.argTypes));
284 			}
285 		}
286 		// now append private functions
287 		foreach (i, func; node.functions){
288 			if (func.visibility == Visibility.Private)
289 				_this.addFunction(Function(func.name, func.returnType, func.argTypes));
290 		}
291 	}
292 	/// Reads all `VarDeclareNode`s from ScriptNode (global variable declarations)
293 	/// 
294 	/// any error is appended to compileErrors
295 	void readGlobVars(ref ScriptNode node){
296 		// check for conflicts among names, and append public 
297 		foreach (ref varDeclare; node.variables){
298 			foreach (varName; varDeclare.vars){
299 				// conflict check
300 				if (_vars.hasVar(varName))
301 					compileErrors.append(CompileError(varDeclare.lineno, "global variable "~varName~" is declared multiple times"));
302 				// append
303 				if (varDeclare.visibility == Visibility.Public){
304 					// just assign it an id, it wont be used anywhere, but why not do it anyways?
305 					varDeclare.setVarID(varName, _vars.vars.length);
306 					_vars.addVar(Variable(varName, varDeclare.type));
307 					_exports.addVar(Variable(varName, varDeclare.type));
308 				}
309 				// check type
310 				if (!isValidType(varDeclare.type))
311 					compileErrors.append(CompileError(varDeclare.lineno, "invalid data type"));
312 			}
313 		}
314 		// now append the private ones
315 		foreach (ref varDeclare; node.variables){
316 			if (varDeclare.visibility == Visibility.Private){
317 				foreach (varName; varDeclare.vars){
318 					varDeclare.setVarID(varName, _vars.vars.length);
319 					_vars.addVar(Variable(varName, varDeclare.type));
320 				}
321 			}
322 		}
323 	}
324 	/// reads all EnumNode from ScriptNode
325 	/// 
326 	/// any error is appended to compileErrors
327 	void readEnums(ScriptNode node){
328 		// check for conflicts, and append public enums
329 		foreach (id; 0 .. node.enums.length){
330 			EnumNode enumA = node.enums[id];
331 			foreach (i; 0 .. id){
332 				EnumNode enumB = node.enums[i];
333 				if (enumA.name == enumB.name)
334 					compileErrors.append(CompileError(enumB.lineno, enumB.name~" is declared multiple times"));
335 			}
336 			if (enumA.visibility == Visibility.Public){
337 				_this.addEnum(enumA.toEnum);
338 				_exports.addEnum(enumA.toEnum);
339 			}
340 		}
341 		// now do private enums
342 		foreach (currentEnum; node.enums){
343 			if (currentEnum.visibility == Visibility.Private)
344 				_this.addEnum(Enum(currentEnum.name, currentEnum.members.dup));
345 		}
346 	}
347 	/// Reads all structs from ScriptNode
348 	/// 
349 	/// Checks for any circular dependency, and other issues. Appends errors to compileErrors
350 	void readStructs(ScriptNode node){
351 		// first check for conflicts in name, and append public struct to _exports
352 		foreach (id, currentStruct; node.structs){
353 			foreach (i; 0 .. id){
354 				if (node.structs[i].name == currentStruct.name)
355 					compileErrors.append(CompileError(currentStruct.lineno, currentStruct.name~" is declared multiple times"));
356 			}
357 			if (currentStruct.visibility == Visibility.Public){
358 				_this.addStruct(currentStruct.toStruct);
359 				_exports.addStruct(currentStruct.toStruct);
360 			}
361 		}
362 		// now add private structs to _this
363 		foreach (currentStruct; node.structs){
364 			if (currentStruct.visibility == Visibility.Private)
365 				_this.addStruct(currentStruct.toStruct);
366 		}
367 		// now look at data types of members
368 		foreach (currentStruct; node.structs){
369 			foreach (i, type; currentStruct.membersDataType){
370 				if (!isValidType(currentStruct.membersDataType[i]))
371 					compileErrors.append(CompileError(currentStruct.lineno,
372 						"struct member "~currentStruct.membersName[i]~" has invalid data type"));
373 			}
374 		}
375 		// now to check for recursive dependency
376 		List!string conflicts = new List!string;
377 		foreach (str; _this.structs)
378 			checkRecursiveDependency(str.name, conflicts);
379 		// add errors for all structs named in conflicts
380 		for (conflicts.seek = 0; conflicts.seek < conflicts.length; ){
381 			immutable string name = conflicts.read();
382 			// locate it's line number
383 			uinteger lineno;
384 			foreach (str; node.structs){
385 				if (str.name == name){
386 					lineno = str.lineno;
387 					break;
388 				}
389 			}
390 			compileErrors.append(CompileError(lineno, "recursive dependency detected"));
391 		}
392 		.destroy(conflicts);
393 	}
394 	/// Checks for circular dependency in a struct
395 	/// `name` is name of struct to check
396 	/// Puts names of structs where conflicts occur in `conflicting`
397 	/// `parents` are the structs that `name` struct is used in (i.e have member of type `name` struct)
398 	void checkRecursiveDependency(string name, List!string conflicting, immutable string[] parents = []){
399 		/// Returns: true if a string is a locally defined struct name
400 		static bool isLocalStruct(string name, Struct[] structs){
401 			foreach (currStr; structs){
402 				if (currStr.name == name)
403 					return true;
404 			}
405 			return false;
406 		}
407 		Struct str;
408 		if (_this.hasStruct(name, str)){
409 			// structs must not be in members' data types
410 			immutable string[] notAllowed = parents ~ cast(immutable string[])[name];
411 			foreach (i; 0 .. str.membersName.length){
412 				string memberTypeName = str.membersDataType[i].typeName;
413 				// if not a locally defined struct, or is a ref, skip
414 				if (!isLocalStruct(memberTypeName, _this.structs) || str.membersDataType[i].isRef)
415 					continue;
416 				if (notAllowed.hasElement(memberTypeName)){
417 					if (conflicting.indexOf(name) < 0)
418 						conflicting.append(name);
419 				}else{
420 					// I used the recursion to destroy recursive dependency
421 					checkRecursiveDependency(str.membersName[i], conflicting, notAllowed);
422 				}
423 			}
424 		}
425 	}
426 	/// Returns: return type for a CodeNode
427 	/// 
428 	/// In case there's an error, returns `DataType()`
429 	DataType getReturnType(CodeNode node){
430 		if (node.returnType != DataType(DataType.Type.Void)){
431 			return node.returnType;
432 		}
433 		if (node.type == CodeNode.Type.FunctionCall){
434 			DataType[] argTypes;
435 			FunctionCallNode fCall = node.node!(CodeNode.Type.FunctionCall);
436 			argTypes.length = fCall.arguments.length;
437 			foreach (i, arg; fCall.arguments){
438 				argTypes[i] = getReturnType(arg);
439 			}
440 			getFunction(fCall.fName, argTypes, fCall.returnType, fCall.id, fCall.libraryId);
441 			return fCall.returnType;
442 		}else if (node.type == CodeNode.Type.Literal){
443 			return node.node!(CodeNode.Type.Literal).returnType;
444 		}else if (node.type == CodeNode.Type.Operator){
445 			OperatorNode opNode = node.node!(CodeNode.Type.Operator);
446 			if (BOOL_OPERATORS.hasElement(opNode.operator))
447 				return DataType(DataType.Type.Int);
448 			return opNode.operands[0].returnType;
449 		}else if (node.type == CodeNode.Type.SOperator){
450 			SOperatorNode opNode = node.node!(CodeNode.Type.SOperator);
451 			DataType operandType = getReturnType(opNode.operand);
452 			// hardcoded 2 lines below, beware
453 			if (opNode.operator == "@")
454 				operandType.isRef = operandType.isRef ? false : true;
455 			// </hardcoded>
456 			return operandType;
457 		}else if (node.type == CodeNode.Type.ReadElement){
458 			ReadElement arrayRead = node.node!(CodeNode.Type.ReadElement);
459 			DataType readFromType = getReturnType(arrayRead.readFromNode);
460 			if (readFromType.arrayDimensionCount == 0){
461 				return DataType();
462 			}
463 			readFromType.arrayDimensionCount --;
464 			return readFromType;
465 		}else if (node.type == CodeNode.Type.Variable){
466 			VariableNode varNode = node.node!(CodeNode.Type.Variable);
467 			getVar(varNode.varName, varNode.returnType, varNode.id, varNode.libraryId, varNode.isGlobal);
468 			return varNode.returnType;
469 		}else if (node.type == CodeNode.Type.Array){
470 			ArrayNode arNode = node.node!(CodeNode.Type.Array);
471 			if (arNode.elements.length == 0){
472 				return DataType(DataType.Type.Void,1);
473 			}
474 			DataType r = getReturnType(arNode.elements[0]);
475 			r.arrayDimensionCount ++;
476 			return r;
477 		}else if (node.type == CodeNode.Type.MemberSelector){
478 			MemberSelectorNode memberSelector = node.node!(CodeNode.Type.MemberSelector);
479 			if (memberSelector.returnType.type == DataType.Type.Void)
480 				checkAST(memberSelector);
481 			return memberSelector.returnType;
482 		}
483 		return DataType(DataType.Type.Void);
484 	}
485 	/// Returns: true if a data type is valid (enum name is a not a valid data type btw, use int)
486 	bool isValidType(ref DataType type){
487 		if (!type.isCustom)
488 			return true;
489 		Struct str;
490 		if (_this.hasStruct(type.typeName, str)){
491 			type.customLength = str.membersName.length;
492 			return true;
493 		}
494 		// now time to search in all libraries' structs names
495 		foreach (integer libId, lib; _libraries){
496 			if (!isImported(libId-1))
497 				continue;
498 			if (lib.hasStruct(type.typeName, str)){
499 				type.customLength = str.membersName.length;
500 				return true;
501 			}
502 		}
503 		return false;
504 	}
505 	/// Returns: true if a library is imported
506 	bool isImported(integer id){
507 		if (id >= _libraries.length)
508 			return false;
509 		if (_libraries[id].autoImport)
510 			return true;
511 		if (id in _isImported)
512 			return _isImported[id];
513 		return false;
514 	}
515 protected:
516 	/// checks if a FunctionNode is valid
517 	void checkAST(ref FunctionNode node){
518 		_vars.scopeIncrease();
519 		_vars.scopeVarCountStart();
520 		functionReturnType = node.returnType;
521 		if (!isValidType(node.returnType))
522 			compileErrors.append(CompileError(node.lineno, "invalid data type"));
523 		// add the arg's to the var scope. make sure one arg name is not used more than once
524 		foreach (i, arg; node.arguments){
525 			if (!addVar(arg.argName, arg.argType)){
526 				compileErrors.append(CompileError(node.lineno, "argument name '"~arg.argName~"' has been used more than once"));
527 			}
528 		}
529 		// now check the statements
530 		checkAST(node.bodyBlock);
531 		_vars.scopeDecrease();
532 		node.varStackCount = _vars.scopeVarCount/* + node.arguments.length*/;
533 	}
534 	/// checks if a StatementNode is valid
535 	void checkAST(ref StatementNode node){
536 		if (node.type == StatementNode.Type.Assignment){
537 			checkAST(node.node!(StatementNode.Type.Assignment));
538 		}else if (node.type == StatementNode.Type.Block){
539 			checkAST(node.node!(StatementNode.Type.Block));
540 		}else if (node.type == StatementNode.Type.DoWhile){
541 			checkAST(node.node!(StatementNode.Type.DoWhile));
542 		}else if (node.type == StatementNode.Type.For){
543 			checkAST(node.node!(StatementNode.Type.For));
544 		}else if (node.type == StatementNode.Type.FunctionCall){
545 			checkAST(node.node!(StatementNode.Type.FunctionCall));
546 		}else if (node.type == StatementNode.Type.If){
547 			checkAST(node.node!(StatementNode.Type.If));
548 		}else if (node.type == StatementNode.Type.VarDeclare){
549 			checkAST(node.node!(StatementNode.Type.VarDeclare));
550 		}else if (node.type == StatementNode.Type.While){
551 			checkAST(node.node!(StatementNode.Type.While));
552 		}else if (node.type == StatementNode.Type.Return){
553 			checkAST(node.node!(StatementNode.Type.Return));
554 		}
555 	}
556 	/// checks a AssignmentNode
557 	void checkAST(ref AssignmentNode node){
558 		// make sure var exists, checkAST(VariableNode) does that
559 		checkAST(node.lvalue);
560 		checkAST(node.rvalue);
561 		const DataType lType = node.lvalue.returnType, rType = node.rvalue.returnType;
562 		// allow only de-ref-ing references
563 		if (node.deref && !lType.isRef){
564 			compileErrors.append(CompileError(node.lvalue.lineno, "can only deref (@) a reference"));
565 		}else{
566 			// go on, do some more tests
567 			if (node.deref){
568 				if (lType.isRef == rType.isRef){
569 					compileErrors.append(CompileError(node.lineno, "rvalue and lvalue data type not matching"));
570 				}
571 			}else{
572 				if (lType.arrayDimensionCount != rType.arrayDimensionCount || !lType.type.canImplicitCast(rType.type) ||
573 				lType.isRef != rType.isRef){
574 					compileErrors.append(CompileError(node.lineno, "rvalue and lvalue data type not matching"));
575 				}
576 			}
577 		}
578 	}
579 	/// checks a BlockNode
580 	void checkAST(ref BlockNode node, bool ownScope = true){
581 		if (ownScope)
582 			_vars.scopeIncrease();
583 		for (uinteger i=0; i < node.statements.length; i ++){
584 			checkAST(node.statements[i]);
585 		}
586 		if (ownScope)
587 			_vars.scopeDecrease();
588 	}
589 	/// checks a DoWhileNode
590 	void checkAST(ref DoWhileNode node){
591 		_vars.scopeIncrease();
592 		checkAST(node.condition);
593 		if (node.condition.returnType.canImplicitCast(DataType(DataType.Type.Bool)))
594 			compileErrors.append(CompileError(node.condition.lineno, "condition must return a bool value"));
595 		if (node.statement.type == StatementNode.Type.Block){
596 			checkAST(node.statement.node!(StatementNode.Type.Block), false);
597 		}else{
598 			checkAST(node.statement);
599 		}
600 		_vars.scopeDecrease();
601 	}
602 	/// checks a ForNode
603 	void checkAST(ref ForNode node){
604 		_vars.scopeIncrease();
605 		// first the init statement
606 		checkAST(node.initStatement);
607 		// then the condition
608 		checkAST(node.condition);
609 		if (!node.condition.returnType.canImplicitCast(DataType(DataType.Type.Bool)))
610 			compileErrors.append(CompileError(node.condition.lineno, "condition must return a bool value"));
611 		// then the increment one
612 		checkAST(node.incStatement);
613 		// finally the loop body
614 		checkAST(node.statement);
615 		_vars.scopeDecrease();
616 	}
617 	/// checks a FunctionCallNode
618 	void checkAST(ref FunctionCallNode node){
619 		/// store the arguments types
620 		DataType[] argTypes;
621 		argTypes.length = node.arguments.length;
622 		// while moving the type into separate array, perform the checks on the args themselves
623 		for (uinteger i=0; i < node.arguments.length; i ++){
624 			checkAST(node.arguments[i]);
625 			argTypes[i] = node.arguments[i].returnType;
626 		}
627 		if (!getFunction(node.fName, argTypes, node.returnType, node.id, node.libraryId))
628 			compileErrors.append(CompileError(node.lineno,
629 					"function "~node.fName~" does not exist or cannot be called with these arguments"));
630 		// do not let it call `this`
631 		else if (node.fName == "this")
632 			compileErrors.append(CompileError(node.lineno, "calling `this` function is not allowed"));
633 	}
634 	/// checks an IfNode
635 	void checkAST(ref IfNode node){
636 		// first the condition
637 		checkAST(node.condition);
638 		if (!node.condition.returnType.canImplicitCast(DataType(DataType.Type.Bool)))
639 			compileErrors.append(CompileError(node.condition.lineno, "condition must return a bool value"));
640 		// then statements
641 		_vars.scopeIncrease();
642 		checkAST(node.statement);
643 		_vars.scopeDecrease();
644 		if (node.hasElse){
645 			_vars.scopeIncrease();
646 			checkAST(node.elseStatement);
647 			_vars.scopeDecrease();
648 		}
649 	}
650 	/// checks a VarDeclareNode
651 	void checkAST(ref VarDeclareNode node){
652 		// check data type
653 		if (!isValidType(node.type))
654 			compileErrors.append(CompileError(node.lineno, "invalid data type"));
655 		foreach (i, varName; node.vars){
656 			if (varExists(varName)){
657 				compileErrors.append(CompileError(node.lineno, "variable "~varName~" has already been declared"));
658 			}else if (node.hasValue(varName)){
659 				// match values
660 				CodeNode* value = &(node.getValue(varName));
661 				checkAST(*value);
662 				// make sure that value can be assigned
663 				if (getReturnType(*value) != node.type){
664 					compileErrors.append(CompileError(node.lineno, "cannot assign value of different data type"));
665 				}
666 			}
667 			// assign it an id
668 			addVar (varName, node.type);
669 			// set it's ID
670 			uinteger vId = getVarID(varName);
671 			node.setVarID(varName, vId);
672 		}
673 	}
674 	/// checks a WhileNode
675 	void checkAST(ref WhileNode node){
676 		// first condition
677 		checkAST(node.condition);
678 		if (!node.condition.returnType.canImplicitCast(DataType(DataType.Type.Bool)))
679 			compileErrors.append(CompileError(node.condition.lineno, "condition must return a bool value"));
680 		// the the statement
681 		_vars.scopeIncrease();
682 		checkAST(node.statement);
683 		_vars.scopeDecrease();
684 	}
685 	/// checks a ReturnNode
686 	void checkAST(ref ReturnNode node){
687 		/// check the value
688 		checkAST(node.value);
689 		if (!node.value.returnType.canImplicitCast(functionReturnType) && 
690 			!(functionReturnType.type == DataType.Type.Void && // let `return null;` be valid for void functions
691 			node.value.returnType.canImplicitCast(DataType(DataType.Type.Void,0,true)))){
692 
693 			compileErrors.append(CompileError(node.value.lineno,"wrong data type for return value"));
694 		}
695 	}
696 	/// checks a CodeNode
697 	void checkAST(ref CodeNode node){
698 		if (node.type == CodeNode.Type.FunctionCall){
699 			checkAST(node.node!(CodeNode.Type.FunctionCall));
700 		}else if (node.type == CodeNode.Type.Literal){
701 			// nothing to check
702 		}else if (node.type == CodeNode.Type.Negative){
703 			checkAST(node.node!(CodeNode.Type.Negative));
704 		}else if (node.type == CodeNode.Type.Operator){
705 			checkAST(node.node!(CodeNode.Type.Operator));
706 		}else if (node.type == CodeNode.Type.SOperator){
707 			checkAST(node.node!(CodeNode.Type.SOperator));
708 		}else if (node.type == CodeNode.Type.ReadElement){
709 			checkAST(node.node!(CodeNode.Type.ReadElement));
710 		}else if (node.type == CodeNode.Type.Variable){
711 			checkAST(node.node!(CodeNode.Type.Variable));
712 		}else if (node.type == CodeNode.Type.Array){
713 			checkAST(node.node!(CodeNode.Type.Array));
714 		}else if (node.type == CodeNode.Type.MemberSelector){
715 			checkAST(node.node!(CodeNode.Type.MemberSelector));
716 		}
717 	}
718 	/// checks a NegativeValueNode
719 	void checkAST(ref NegativeValueNode node){
720 		// check the val
721 		checkAST(node.value);
722 		// make sure data type is either double or integer, nothing else works
723 		CompileError err = CompileError(node.lineno, "can only use - operator on numerical data types");
724 		if (node.value.returnType.isArray || node.returnType.isRef)
725 			compileErrors.append(err);
726 		else if (!node.value.returnType.type.canImplicitCast(DataType.Type.Int) && 
727 			!node.value.returnType.type.canImplicitCast(DataType.Type.Double))
728 			compileErrors.append(err);
729 	}
730 	/// checks a OperatorNode
731 	void checkAST(ref OperatorNode node){
732 		if (node.operator !in OPERATOR_FUNCTIONS){
733 			compileErrors.append(CompileError(node.lineno, "operator has no corresponding function"));
734 			return;
735 		}
736 		node.fCall = FunctionCallNode(OPERATOR_FUNCTIONS[node.operator], node.operands);
737 		node.fCall.lineno = node.lineno;
738 		// just use the FunctionCallNode
739 		checkAST(node.fCall);
740 	}
741 	/// checks a SOperatorNode
742 	void checkAST(ref SOperatorNode node){
743 		if (node.operator !in OPERATOR_FUNCTIONS){
744 			compileErrors.append(CompileError(node.lineno, "operator has no corresponding function"));
745 			return;
746 		}
747 		if (node.operator == "@"){
748 			checkAST(node.operand);
749 			DataType type = node.operand.returnType;
750 			type.isRef = !type.isRef;
751 			node.returnType = type;
752 			// do not allow ref from or deref from any type involving void
753 			if (type.type == DataType.Type.Void)
754 				compileErrors.append(CompileError(node.lineno, "Cannot use @ operator on a void"));
755 			return;
756 		}
757 		node.fCall = FunctionCallNode(OPERATOR_FUNCTIONS[node.operator], [node.operand]);
758 		node.fCall.lineno = node.lineno;
759 		checkAST(node.fCall);
760 	}
761 	/// checks a ReadElement
762 	void checkAST(ref ReadElement node){
763 		// check the var, and the index
764 		checkAST (node.readFromNode);
765 		checkAST (node.index);
766 		// index must return int
767 		if (getReturnType(node.index).canImplicitCast(DataType(DataType.Type.Int))){
768 			compileErrors.append (CompileError(node.index.lineno,
769 				"cannot implicitly cast "~getReturnType(node.index).name~" to uint"));
770 		}
771 		// now make sure that the data is an array or a string
772 		immutable DataType readFromType = getReturnType (node.readFromNode);
773 		if (readFromType.arrayDimensionCount == 0)
774 			compileErrors.append (CompileError(node.readFromNode.lineno, "cannnot use [..] on non-array data"));
775 		if (readFromType.isRef)
776 			compileErrors.append (CompileError(node.readFromNode.lineno, "cannot use [..] on references"));
777 		node.returnType = readFromType;
778 		node.returnType.arrayDimensionCount --;
779 	}
780 	/// checks a VariableNode
781 	void checkAST(ref VariableNode node){
782 		// make sure that that var was declared
783 		if (!varExists(node.varName)){
784 			compileErrors.append (CompileError(node.lineno,"variable "~node.varName~" not declared but used"));
785 		}
786 		// just call this to write struct size just in case it is a struct
787 		isValidType(node.returnType);
788 		// and put the assigned ID to it
789 		getVar(node.varName, node.returnType, node.id, node.libraryId, node.isGlobal);
790 	}
791 	/// checks an ArrayNode
792 	void checkAST(ref ArrayNode node){
793 		// check each element, and make sure all their types are same
794 		if (node.elements.length > 0){
795 			checkAST(node.elements[0]);
796 			DataType type = getReturnType(node.elements[0]);
797 			bool typeMatches = true;
798 			for (uinteger i=1; i < node.elements.length; i ++){
799 				checkAST (node.elements[i]);
800 				if (typeMatches && !node.elements[i].returnType.canImplicitCast(type)){
801 					compileErrors.append (CompileError(node.elements[i].lineno, "elements in array must be of same type"));
802 					typeMatches = false;
803 				}
804 			}
805 			node.returnType = type;
806 			node.returnType.arrayDimensionCount ++;
807 		}else
808 			node.returnType = DataType(DataType.Type.Void,1);
809 	}
810 	/// checks a MemberSelectorNode
811 	void checkAST(ref MemberSelectorNode node){
812 		// first check parent, could be an enum, so first match names
813 		if (node.parent.type == CodeNode.Type.Variable){
814 			Enum enumData;
815 			string enumName = node.parent.node!(CodeNode.Type.Variable).varName;
816 			if (getEnum(enumName, enumData)){
817 				node.memberNameIndex = enumData.members.indexOf(node.memberName);
818 				if (node.memberNameIndex < 0){
819 					compileErrors.append(CompileError(node.lineno, "enum "~enumName~" has no member named "~node.memberName));
820 				}
821 				node.type = MemberSelectorNode.Type.EnumMemberRead;
822 				node.returnType = DataType(DataType.Type.Int);
823 				return;
824 			}
825 		}
826 		checkAST(node.parent);
827 		string parentDataTypeName = node.parent.returnType.name;
828 		Struct parentStructType;
829 		// not gonna work if its a reference to that type, or an array
830 		if (getStruct(parentDataTypeName, parentStructType)){
831 			// check if that struct has some member of that name
832 			node.memberNameIndex = parentStructType.membersName.indexOf(node.memberName);
833 			if (node.memberNameIndex > -1){
834 				node.type = MemberSelectorNode.Type.StructMemberRead;
835 				node.returnType = parentStructType.membersDataType[node.memberNameIndex];
836 			}else
837 				compileErrors.append(
838 					CompileError(node.lineno, "no member with name "~node.memberName~" exists in struct "~parentDataTypeName));
839 		}else{
840 			compileErrors.append(CompileError(node.parent.lineno, "invalid data type of operand for . operator"));
841 		}
842 	}
843 public:
844 	/// constructor
845 	this (Library[] libraries){
846 		compileErrors = new LinkedList!CompileError;
847 		_libraries = libraries.dup;
848 		_vars = new VarStore();
849 	}
850 	~this(){
851 		.destroy(compileErrors);
852 		.destroy(_vars);
853 	}
854 	/// checks a script's AST for any errors
855 	/// 
856 	/// Arguments:
857 	/// `node` is the ScriptNode for the script  
858 	/// `scriptFunctions` is the array to put data about script defined functions in
859 	/// 
860 	/// Returns: errors in CompileError[] or just an empty array if there were no errors
861 	CompileError[] checkAST(ref ScriptNode node, Library exports, Library allDeclerations){
862 		// empty everything
863 		compileErrors.clear;
864 		_vars.clear;
865 		_exports = exports;
866 		_this = allDeclerations;
867 		readImports(node);
868 		readEnums(node);
869 		readStructs(node);
870 		readGlobVars(node);
871 		readFunctions(node);
872 		// call checkAST on every FunctionNode
873 		for (uinteger i=0; i < node.functions.length; i++){
874 			checkAST(node.functions[i]);
875 			node.functions[i].id = i; // set the id
876 		}
877 		_exports = null;
878 		_this = null;
879 		CompileError[] r = compileErrors.toArray;
880 		.destroy(compileErrors);
881 		return r;
882 	}
883 	/// checks a script's AST for any errors
884 	CompileError[] checkAST(ref ScriptNode node){
885 		Library lib = new Library("THIS"), priv = new Library("_THIS");
886 		CompileError[] r = checkAST(node, lib, priv);
887 		.destroy(lib);
888 		.destroy(priv);
889 		return r;
890 	}
891 }