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.misc;
7 import qscript.compiler.ast;
8 
9 import utils.misc;
10 import utils.lists;
11 
12 /// Contains functions to check ASTs for errors  
13 /// One instance of this class can be used to check for errors in a script's AST.
14 class ASTCheck{
15 private:
16 	/// stores the pre defined functions' names, arg types, and return types
17 	Function[] preDefFunctions;
18 	/// stores the script defined functions' names, arg types, and return types
19 	Function[] scriptDefFunctions;
20 	/// stores all the errors that are there in the AST being checked
21 	LinkedList!CompileError compileErrors;
22 	/// stores data types of variables in currently-being-checked-FunctionNode
23 	DataType[string] varTypes;
24 	/// stores the IDs (as index) for vars
25 	string[uinteger] varIDs;
26 	/// stores the scope-depth of each var in currently-being-checked-FunctionNode which is currently in scope
27 	uinteger[string] varScope;
28 	/// stores expected return type of currently-being-checked function
29 	DataType functionReturnType;
30 	/// the largest variable id in currectly being-checked function
31 	integer maxVarId;
32 	/// stores current scope-depth
33 	uinteger scopeDepth;
34 	/// registers a new var in current scope
35 	/// 
36 	/// Returns: false if it was already registered, true if it was successful
37 	bool addVar(string name, DataType type){
38 		if (name in varTypes){
39 			return false;
40 		}
41 		varTypes[name] = type;
42 		varScope[name] = scopeDepth;
43 
44 		uinteger i;
45 		for (i = 0; ; i ++){
46 			if (i !in varIDs)
47 				break;
48 		}
49 		varIDs[i] = name;
50 		return true;
51 	}
52 	/// Returns: the data type of a variable  
53 	/// or if var does not exist, returns `DataType()`
54 	DataType getVarType(string name){
55 		if (name in varTypes){
56 			return varTypes[name];
57 		}
58 		return DataType();
59 	}
60 	/// Returns: the ID for a variable  
61 	/// or -1 if it does not exist
62 	integer getVarID(string name){
63 		foreach (id, varName; varIDs){
64 			if (name == varName){
65 				return id;
66 			}
67 		}
68 		return -1;
69 	}
70 	/// Returns: true if a var is available in current scope
71 	bool varExists(string name){
72 		if (name in varTypes)
73 			return true;
74 		return false;
75 	}
76 	/// increases the scope-depth
77 	/// 
78 	/// this function should be called before any AST-checking in a sub-BlockNode or anything like that is started
79 	void increaseScope(){
80 		scopeDepth ++;
81 	}
82 	/// decreases the scope-depth
83 	/// 
84 	/// this function should be called right after checking in a sub-BlockNode or anything like that is finished  
85 	/// it will put the vars declared inside that scope go out of scope
86 	void decreaseScope(){
87 		foreach (key; varScope.keys){
88 			if (varScope[key] == scopeDepth){
89 				varScope.remove(key);
90 				// remove from varDataTypes too!
91 				varTypes.remove(key);
92 				// remove it from varIDs too
93 				foreach (id; varIDs.keys){
94 					if (varIDs[id] == key){
95 						varIDs.remove (id);
96 						break;
97 					}
98 				}
99 			}
100 		}
101 		if (scopeDepth > 0){
102 			scopeDepth --;
103 		}
104 	}
105 	/// Returns: return type of a function, works for both script-defined and predefined functions  
106 	/// or if the function does not exist, it'll return `DataType()`
107 	DataType getFunctionType(string name, DataType[] argTypes){
108 		foreach (func; scriptDefFunctions){
109 			if (func.name == name && func.argTypes == argTypes){
110 				return func.returnType;
111 			}
112 		}
113 		foreach (func; preDefFunctions){
114 			if (func.name == name && func.argTypes == argTypes){
115 				return func.returnType;
116 			}
117 		}
118 		return DataType();
119 	}
120 	/// Returns: return type for a CodeNode
121 	/// 
122 	/// In case there's an error, returns `DataType()`
123 	DataType getReturnType(CodeNode node){
124 		if (node.returnType != DataType(DataType.Type.Void)){
125 			return node.returnType;
126 		}
127 		if (node.type == CodeNode.Type.FunctionCall){
128 			DataType[] argTypes;
129 			FunctionCallNode fCall = node.node!(CodeNode.Type.FunctionCall);
130 			argTypes.length = fCall.arguments.length;
131 			foreach (i, arg; fCall.arguments){
132 				argTypes[i] = getReturnType(arg);
133 			}
134 			node.returnType = getFunctionType(fCall.fName, argTypes);
135 			return node.returnType;
136 		}else if (node.type == CodeNode.Type.Literal){
137 			return node.node!(CodeNode.Type.Literal).returnType;
138 		}else if (node.type == CodeNode.Type.Operator){
139 			OperatorNode opNode = node.node!(CodeNode.Type.Operator);
140 			if (BOOL_OPERATORS.hasElement(opNode.operator))
141 				return DataType(DataType.Type.Integer);
142 			return opNode.operands[0].returnType;
143 		}else if (node.type == CodeNode.Type.SOperator){
144 			SOperatorNode opNode = node.node!(CodeNode.Type.SOperator);
145 			DataType operandType = getReturnType(opNode.operand);
146 			// hardcoded stuff, beware
147 			if (opNode.operator == "@"){
148 				operandType.isRef = operandType.isRef ? false : true;
149 			}
150 			// </hardcoded>
151 			return operandType;
152 		}else if (node.type == CodeNode.Type.ReadElement){
153 			ReadElement arrayRead = node.node!(CodeNode.Type.ReadElement);
154 			DataType readFromType = getReturnType(arrayRead.readFromNode);
155 			if (readFromType.arrayDimensionCount == 0){
156 				if (readFromType.type == DataType.Type.String){
157 					return DataType(DataType.Type.String);
158 				}else{
159 					return DataType();
160 				}
161 			}
162 			readFromType.arrayDimensionCount --;
163 			return readFromType;
164 		}else if (node.type == CodeNode.Type.Variable){
165 			VariableNode varNode = node.node!(CodeNode.Type.Variable);
166 			return getVarType(varNode.varName);
167 		}else if (node.type == CodeNode.Type.Array){
168 			ArrayNode arNode = node.node!(CodeNode.Type.Array);
169 			if (arNode.elements.length == 0){
170 				return DataType(DataType.Type.Void,1);
171 			}
172 			DataType r = getReturnType(arNode.elements[0]);
173 			r.arrayDimensionCount ++;
174 			return r;
175 		}
176 		return DataType(DataType.Type.Void);
177 	}
178 	/// reads all FunctionNode from ScriptNode and writes them into `scriptDefFunctions`
179 	/// 
180 	/// any error is appended to compileErrors
181 	void readFunctions(ScriptNode node){
182 		/// stores the functions in byte code style, coz that way, its easier to check if same function with same
183 		/// arg types has been used more than once
184 		string[] byteCodefunctionNames;
185 		scriptDefFunctions.length = node.functions.length;
186 		byteCodefunctionNames.length = node.functions.length;
187 		foreach (i, func; node.functions){
188 			// read arg types into a single array
189 			DataType[] argTypes;
190 			argTypes.length = func.arguments.length;
191 			foreach (index, arg; func.arguments)
192 				argTypes[index] = arg.argType;
193 			scriptDefFunctions[i] = Function(func.name, func.returnType, argTypes);
194 			byteCodefunctionNames[i] = encodeFunctionName(func.name, argTypes);
195 			if (byteCodefunctionNames[0 .. i].hasElement(byteCodefunctionNames[i])){
196 				compileErrors.append (CompileError(func.lineno,
197 						"functions with same name must have different argument types"
198 					));
199 			}
200 		}
201 	}
202 protected:
203 	/// checks if a FunctionNode is valid
204 	void checkAST(ref FunctionNode node){
205 		maxVarId = (cast(integer)node.arguments.length) - 1;
206 		increaseScope();
207 		functionReturnType = node.returnType;
208 		// add the arg's to the var scope. make sure one arg name is not used more than once
209 		foreach (i, arg; node.arguments){
210 			if (!addVar(arg.argName, arg.argType)){
211 				compileErrors.append(CompileError(node.lineno, "argument name '"~arg.argName~"' has been used more than once"));
212 			}
213 		}
214 		// now check the statements
215 		checkAST(node.bodyBlock);
216 		node.varCount = maxVarId + 1;
217 		decreaseScope();
218 	}
219 	/// checks if a StatementNode is valid
220 	void checkAST(ref StatementNode node){
221 		if (node.type == StatementNode.Type.Assignment){
222 			checkAST(node.node!(StatementNode.Type.Assignment));
223 		}else if (node.type == StatementNode.Type.Block){
224 			checkAST(node.node!(StatementNode.Type.Block));
225 		}else if (node.type == StatementNode.Type.DoWhile){
226 			checkAST(node.node!(StatementNode.Type.DoWhile));
227 		}else if (node.type == StatementNode.Type.For){
228 			checkAST(node.node!(StatementNode.Type.For));
229 		}else if (node.type == StatementNode.Type.FunctionCall){
230 			checkAST(node.node!(StatementNode.Type.FunctionCall));
231 		}else if (node.type == StatementNode.Type.If){
232 			checkAST(node.node!(StatementNode.Type.If));
233 		}else if (node.type == StatementNode.Type.VarDeclare){
234 			checkAST(node.node!(StatementNode.Type.VarDeclare));
235 		}else if (node.type == StatementNode.Type.While){
236 			checkAST(node.node!(StatementNode.Type.While));
237 		}else if (node.type == StatementNode.Type.Return){
238 			checkAST(node.node!(StatementNode.Type.Return));
239 		}
240 	}
241 	/// checks a AssignmentNode
242 	void checkAST(ref AssignmentNode node){
243 		// make sure var exists, checkAST(VariableNode) does that
244 		checkAST(node.var);
245 		checkAST(node.val);
246 		// now check the CodeNode's for indexes
247 		for (uinteger i=0; i < node.indexes.length; i++){
248 			checkAST(node.indexes[i]);
249 		}
250 		DataType varType = getVarType (node.var.varName);
251 		// check if the indexes provided for the var are possible, i.e if the var declaration has >= those indexes
252 		if (varType.arrayDimensionCount < node.indexes.length){
253 			compileErrors.append(CompileError(node.var.lineno, "array's dimension count in assignment differes from declaration"));
254 		}else{
255 			// check if the data type of the value and var (considering the indexes used) match
256 			DataType expectedType = varType;
257 			expectedType.arrayDimensionCount -= node.indexes.length;
258 			// check with deref (if has to deref the var)
259 			if (node.deref){
260 				expectedType.isRef = false;
261 				if (!varType.isRef){
262 					compileErrors.append (CompileError(node.val.lineno, "can only deref (@) a reference"));
263 				}
264 			}
265 			if (getReturnType(node.val) != expectedType){
266 				compileErrors.append(CompileError(node.val.lineno, "cannot assign value with different data type to variable"));
267 			}
268 		}
269 	}
270 	/// checks a BlockNode
271 	void checkAST(ref BlockNode node, bool ownScope = true){
272 		if (ownScope)
273 			increaseScope();
274 		for (uinteger i=0; i < node.statements.length; i ++){
275 			checkAST(node.statements[i]);
276 		}
277 		if (ownScope)
278 			decreaseScope();
279 	}
280 	/// checks a DoWhileNode
281 	void checkAST(ref DoWhileNode node){
282 		increaseScope();
283 		checkAST(node.condition);
284 		if (node.statement.type == StatementNode.Type.Block){
285 			checkAST(node.statement.node!(StatementNode.Type.Block), false);
286 		}else{
287 			checkAST(node.statement);
288 		}
289 		decreaseScope();
290 	}
291 	/// checks a ForNode
292 	void checkAST(ref ForNode node){
293 		increaseScope();
294 		// first the init statement
295 		checkAST(node.initStatement);
296 		// then the condition
297 		checkAST(node.condition);
298 		// then the increment one
299 		checkAST(node.incStatement);
300 		// finally the loop body
301 		checkAST(node.statement);
302 		decreaseScope();
303 	}
304 	/// checks a FunctionCallNode
305 	void checkAST(ref FunctionCallNode node){
306 		/// store the arguments types
307 		DataType[] argTypes;
308 		argTypes.length = node.arguments.length;
309 		// while moving the type into separate array, perform the checks on the args themselves
310 		for (uinteger i=0; i < node.arguments.length; i ++){
311 			checkAST(node.arguments[i]);
312 			argTypes[i] = node.arguments[i].returnType;
313 		}
314 		// now make sure that that function exists, and the arg types match
315 		bool functionExists = false;
316 		foreach (i, func; scriptDefFunctions){
317 			if (func.name == node.fName && func.argTypes == argTypes){
318 				functionExists = true;
319 				node.isScriptDefined = true;
320 				node.id = i;
321 				node.returnType = func.returnType;
322 			}
323 		}
324 		if (!functionExists){
325 			foreach (i, func; preDefFunctions~INBUILT_FUNCTIONS){
326 				if (func.name == node.fName && matchArguments(func.argTypes, argTypes)){
327 					functionExists = true;
328 					node.isScriptDefined = false;
329 					if (i >= preDefFunctions.length)
330 						node.isInBuilt = true;
331 					node.id = i;
332 					node.returnType = func.returnType;
333 				}
334 			}
335 		}
336 		if (!functionExists){
337 			compileErrors.append(CompileError(node.lineno,
338 					"function "~node.fName~" does not exist or cannot be called with these arguments"));
339 		}
340 	}
341 	/// checks an IfNode
342 	void checkAST(ref IfNode node){
343 		// first the condition
344 		checkAST(node.condition);
345 		// then statements
346 		increaseScope();
347 		checkAST(node.statement);
348 		decreaseScope();
349 		if (node.hasElse){
350 			increaseScope();
351 			checkAST(node.elseStatement);
352 			decreaseScope();
353 		}
354 	}
355 	/// checks a VarDeclareNode
356 	void checkAST(ref VarDeclareNode node){
357 		foreach (i, varName; node.vars){
358 			if (varExists(varName)){
359 				compileErrors.append(CompileError(node.lineno, "variable "~varName~" has already been declared"));
360 			}else if (node.hasValue(varName)){
361 				// match values
362 				CodeNode value = node.getValue(varName);
363 				checkAST(value);
364 				// make sure that value can be assigned
365 				if (getReturnType(value) != node.type){
366 					compileErrors.append(CompileError(node.lineno, "cannot assign value of different data type"));
367 				}
368 			}
369 			// assign it an id
370 			addVar (varName, node.type);
371 			// set it's ID
372 			uinteger vId = getVarID(varName);
373 			if (cast(integer)vId > maxVarId)
374 				maxVarId = cast(integer)vId;
375 			node.setVarID(varName, vId);
376 		}
377 	}
378 	/// checks a WhileNode
379 	void checkAST(ref WhileNode node){
380 		// first condition
381 		checkAST(node.condition);
382 		// the the statement
383 		increaseScope();
384 		checkAST(node.statement);
385 		decreaseScope();
386 	}
387 	/// checks a ReturnNode
388 	void checkAST(ref ReturnNode node){
389 		/// check the value
390 		checkAST(node.value);
391 		if (node.value.returnType != functionReturnType || functionReturnType.type == DataType.Type.Void){
392 			compileErrors.append(CompileError(node.value.lineno,"wrong data type for return value"));
393 		}
394 	}
395 	/// checks a CodeNode
396 	void checkAST(ref CodeNode node){
397 		if (node.type == CodeNode.Type.FunctionCall){
398 			checkAST(node.node!(CodeNode.Type.FunctionCall));
399 		}else if (node.type == CodeNode.Type.Literal){
400 			// nothing to check
401 		}else if (node.type == CodeNode.Type.Operator){
402 			checkAST(node.node!(CodeNode.Type.Operator));
403 		}else if (node.type == CodeNode.Type.SOperator){
404 			checkAST(node.node!(CodeNode.Type.SOperator));
405 		}else if (node.type == CodeNode.Type.ReadElement){
406 			checkAST(node.node!(CodeNode.Type.ReadElement));
407 		}else if (node.type == CodeNode.Type.Variable){
408 			checkAST(node.node!(CodeNode.Type.Variable));
409 		}else if (node.type == CodeNode.Type.Array){
410 			checkAST(node.node!(CodeNode.Type.Array));
411 		}
412 	}
413 	/// checks a OperatorNode
414 	void checkAST(ref OperatorNode node){
415 		// check the operands
416 		for (uinteger i=0; i < node.operands.length; i ++){
417 			checkAST (node.operands[i]);
418 		}
419 		// make sure both operands are of same data type
420 		DataType operandType = getReturnType(node.operands[0]);
421 		if (getReturnType(node.operands[0]) != getReturnType(node.operands[1])){
422 			compileErrors.append(CompileError(node.lineno, "Both operands for operator must be of same type"));
423 		}
424 		// now make sure that the data type of operands is allowed with that operator
425 		if (["+","-","*","/","%", "<", ">"].hasElement(node.operator)){
426 			// only double and int allowed
427 			if (operandType != DataType(DataType.Type.Integer) && operandType != DataType(DataType.Type.Double)){
428 				compileErrors.append (CompileError(node.lineno, "that operator can only be used on double or int"));
429 			}
430 			node.returnType = operandType;
431 		}else if (["&&", "||"].hasElement(node.operator)){
432 			if (operandType != DataType(DataType.Type.Integer)){
433 				compileErrors.append (CompileError(node.lineno, "that operator can only be used on int"));
434 			}
435 			node.returnType = DataType(DataType.Type.Integer);
436 		}else if (node.operator == "~"){
437 			if (operandType != DataType(DataType.Type.String) && operandType.arrayDimensionCount == 0){
438 				compileErrors.append (CompileError(node.lineno, "~ operator can only be used on strings and arrays"));
439 			}
440 			node.returnType = operandType;
441 		}
442 	}
443 	/// checks a SOperatorNode
444 	void checkAST(ref SOperatorNode node){
445 		// check the operand
446 		checkAST(node.operand);
447 		// now it it's `!`, only accept int, if `@`, var
448 		if (node.operator == "!"){
449 			if (getReturnType(node.operand) != DataType(DataType.Type.Integer)){
450 				compileErrors.append (CompileError(node.operand.lineno, "cannot use ! on a non-int data type"));
451 			}
452 			node.returnType = DataType(DataType.Type.Integer);
453 		}else if (node.operator == "@"){
454 			node.returnType = node.operand.returnType;
455 			node.returnType.isRef = !node.returnType.isRef;
456 			// if it's used to get a ref, it can only be used on variables, nothing else.
457 			if (!node.operand.returnType.isRef && node.operand.type != CodeNode.Type.Variable){
458 				// could be that it's getting ref of ReadElement, check it that's the case
459 				CodeNode subNode = node.operand;
460 				while (subNode.type == CodeNode.Type.ReadElement){
461 					subNode = subNode.node!(CodeNode.Type.ReadElement).readFromNode;
462 				}
463 				if (subNode.type != CodeNode.Type.Variable)
464 					compileErrors.append(CompileError(node.lineno, "@ can only be used to get reference of variables"));
465 			}
466 		}else
467 			compileErrors.append (CompileError(node.lineno, "invalid operator"));
468 	}
469 	/// checks a ReadElement
470 	void checkAST(ref ReadElement node){
471 		// check the var, and the index
472 		checkAST (node.readFromNode);
473 		checkAST (node.index);
474 		// index must return int
475 		if (getReturnType(node.index) != DataType(DataType.Type.Integer)){
476 			compileErrors.append (CompileError(node.index.lineno, "index must return integer"));
477 		}
478 		// now make sure that the data is an array or a string
479 		DataType readFromType = getReturnType (node.readFromNode);
480 		if (readFromType.arrayDimensionCount == 0 && readFromType.type != DataType.Type.String){
481 			compileErrors.append (CompileError(node.readFromNode.lineno, "cannnot use [..] on non-string non-array data"));
482 		}
483 		node.returnType = readFromType;
484 		node.returnType.arrayDimensionCount --;
485 		/// `[..]` can not be used on refs
486 		if (readFromType.isRef){
487 			compileErrors.append (CompileError(node.readFromNode.lineno, "cannot use [..] on references"));
488 		}
489 	}
490 	/// checks a VariableNode
491 	void checkAST(ref VariableNode node){
492 		// make sure that that var was declared
493 		if (!varExists(node.varName)){
494 			compileErrors.append (CompileError(node.lineno,"variable "~node.varName~" not declared but used"));
495 		}
496 		// and put the assigned ID to it
497 		node.id = getVarID(node.varName);
498 		// set it's type
499 		node.returnType = getVarType(node.varName);
500 	}
501 	/// checks an ArrayNode
502 	void checkAST(ref ArrayNode node){
503 		// check each element, and make sure all their types are same
504 		if (node.elements.length > 0){
505 			DataType type = getReturnType(node.elements[0]);
506 			bool typeMatches = true;
507 			for (uinteger i=0; i < node.elements.length; i ++){
508 				checkAST (node.elements[i]);
509 				if (typeMatches && node.elements[i].returnType != type){
510 					compileErrors.append (CompileError(node.elements[i].lineno, "elements in array must be of same type"));
511 					typeMatches = false;
512 				}
513 			}
514 			node.returnType = type;
515 			node.returnType.arrayDimensionCount ++;
516 		}else
517 			node.returnType = DataType(DataType.Type.Void,1);
518 	}
519 public:
520 	this (Function[] predefinedFunctions){
521 		compileErrors = new LinkedList!CompileError;
522 		preDefFunctions = predefinedFunctions.dup;
523 	}
524 	~this(){
525 		.destroy(compileErrors);
526 	}
527 	/// checks a script's AST for any errors
528 	/// 
529 	/// Arguments:
530 	/// `node` is the ScriptNode for the script
531 	/// 
532 	/// Returns: errors in CompileError[] or just an empty array if there were no errors
533 	CompileError[] checkAST(ref ScriptNode node){
534 		// empty everything
535 		scriptDefFunctions = [];
536 		compileErrors.clear;
537 		varTypes.clear;
538 		varScope.clear;
539 		scopeDepth = 0;
540 		readFunctions(node);
541 		// call checkAST on every FunctionNode
542 		for (uinteger i=0; i < node.functions.length; i++){
543 			checkAST(node.functions[i]);
544 			node.functions[i].id = i; // set the id
545 		}
546 		CompileError[] r = compileErrors.toArray;
547 		.destroy(compileErrors);
548 		return r;
549 	}
550 	/// checks a script's AST for any errors
551 	/// 
552 	/// Arguments:
553 	/// `node` is the ScriptNode for the script  
554 	/// `scriptFunctions` is the array to put data about script defined functions in
555 	/// 
556 	/// Returns: errors in CompileError[], or empty array if there were no errors
557 	CompileError[] checkAST(ref ScriptNode node, ref Function[] scriptFunctions){
558 		CompileError[] errors = checkAST(node);
559 		scriptFunctions = scriptDefFunctions.dup;
560 		return errors;
561 	}
562 }