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 }