1 /++ 2 Contains functions to generate AST from tokens 3 +/ 4 module qscript.compiler.astgen; 5 6 import qscript.compiler.compiler; 7 import qscript.compiler.tokengen; 8 import qscript.compiler.ast; 9 10 import utils.misc; 11 import utils.lists; 12 13 import std.conv : to; 14 15 /// contains functions and stuff to convert a QScript from tokens to Syntax Trees 16 public class ASTGen{ 17 private: 18 /// stores the list of tokens 19 TokenList tokens; 20 /// stores the current index (of tokens) 21 uinteger index; 22 /// stores all compilation errors 23 LinkedList!CompileError compileErrors; 24 /// Reads a single import statement. 25 /// 26 /// Returns: the imported libraries' names 27 string[] readImports(){ 28 string[] r; 29 if (tokens.tokens[index] != Token(Token.Type.Keyword, "import")){ 30 compileErrors.append(CompileError(tokens.getTokenLine(index), "not an import statement")); 31 return []; 32 } 33 index ++; // skip the import 34 while (tokens.tokens[index].type != Token.Type.StatementEnd){ 35 if (tokens.tokens[index].type == Token.Type.Identifier){ 36 r ~= tokens.tokens[index].token; 37 }else if (tokens.tokens[index].type != Token.Type.Comma){ 38 compileErrors.append(CompileError(tokens.getTokenLine(index), "invalid import statement")); 39 } 40 index ++; 41 } 42 index ++; // skip the semicolon 43 44 return r; 45 } 46 /// reads a type from TokensList 47 /// 48 /// returns type in DataType struct, changes `index` to token after last token of type 49 DataType readType(){ 50 uinteger startIndex = index; 51 // check if it's a ref 52 if (tokens.tokens[index].token == "@") 53 index ++; 54 // the first token has to be the type (int, string, double) 55 index ++; 56 // then comes the brackets making it an array 57 if (tokens.tokens[index].type == Token.Type.IndexBracketOpen){ 58 for (; index < tokens.tokens.length; index ++){ 59 if (tokens.tokens[index].type != Token.Type.IndexBracketOpen && 60 tokens.tokens[index].type != Token.Type.IndexBracketClose){ 61 break; 62 } 63 } 64 } 65 string sType = tokens.toString(tokens.tokens[startIndex .. index]); 66 return DataType(sType); 67 } 68 protected: 69 /// generates AST for a struct definition 70 /// 71 /// changes `index` to next token after struct definition 72 StructNode generateStructAST(){ 73 StructNode structNode; 74 if (tokens.tokens[index] == Token(Token.Type.Keyword, "struct")){ 75 structNode.lineno = tokens.getTokenLine(index); 76 // read the name 77 index ++; 78 if (tokens.tokens[index].type == Token.Type.Identifier){ 79 structNode.name = tokens.tokens[index].token; 80 // now check the { and read members 81 index ++; 82 if (tokens.tokens[index].type == Token.Type.BlockStart){ 83 // now start reading it all as VarDeclareNodes 84 index ++; 85 while (index + 1 < tokens.tokens.length && tokens.tokens[index].type != Token.Type.BlockEnd){ 86 VarDeclareNode vars = generateVarDeclareAST(); 87 // make sure no members were assignned values, thats not allowed 88 foreach (memberName; vars.vars){ 89 if (vars.hasValue(memberName)){ 90 compileErrors.append(CompileError(tokens.getTokenLine(index), 91 "cannot assign value to members in struct definition")); 92 } 93 // make sure same member name isn't used >1 times 94 if (structNode.membersName.hasElement(memberName)){ 95 compileErrors.append(CompileError(tokens.getTokenLine(index), 96 "member '"~memberName~"' is declared multiple times in struct")); 97 }else{ 98 structNode.membersName ~= memberName; 99 structNode.membersDataType ~= vars.type; 100 } 101 } 102 } 103 index ++; // skip the } 104 // now check if struct has any members 105 if (structNode.membersName.length == 0){ 106 compileErrors.append(CompileError(tokens.getTokenLine(index), "struct has no members")); 107 } 108 // if there is a semicolon, skip it 109 if (index + 1 < tokens.tokens.length && tokens.tokens[index].type == Token.Type.StatementEnd) 110 index ++; 111 }else{ 112 compileErrors.append(CompileError(tokens.getTokenLine(index), "invalid struct definition, '{' expected")); 113 } 114 }else{ 115 compileErrors.append(CompileError(tokens.getTokenLine(index), "struct name expected")); 116 } 117 }else{ 118 compileErrors.append(CompileError(tokens.getTokenLine(index), "not a struct definition")); 119 index ++; 120 } 121 return structNode; 122 } 123 /// generates AST for a enum definition 124 /// 125 /// changes `index` to next token after enum definition 126 EnumNode generateEnumAST(){ 127 EnumNode enumNode; 128 if (tokens.tokens[index] == Token(Token.Type.Keyword, "enum")){ 129 enumNode.lineno = tokens.getTokenLine(index); 130 index ++; 131 if (tokens.tokens[index].type == Token.Type.Identifier){ 132 enumNode.name = tokens.tokens[index].token; 133 index ++; 134 if (index + 1 < tokens.tokens.length && tokens.tokens[index].type == Token.Type.BlockStart){ 135 index ++; 136 // now start reading members 137 while (tokens.tokens[index].type != Token.Type.BlockEnd){ 138 if (tokens.tokens[index].type == Token.Type.Identifier){ 139 enumNode.members ~= tokens.tokens[index].token; 140 index ++; 141 // expect a } or a comma 142 if (tokens.tokens[index].type == Token.Type.Comma){ 143 index++; 144 }else if (tokens.tokens[index].type != Token.Type.BlockEnd){ 145 compileErrors.append(CompileError(tokens.getTokenLine(index),"enum members must be separated using comma")); 146 } 147 }else{ 148 compileErrors.append(CompileError(tokens.getTokenLine(index), "enum member name expected")); 149 } 150 } 151 index ++; // skip the } 152 // skip the optional semicolon 153 if (index + 1 < tokens.tokens.length && tokens.tokens[index].type == Token.Type.StatementEnd) 154 index ++; 155 }else{ 156 compileErrors.append(CompileError(tokens.getTokenLine(index), "invalid enum definition, '{' expected")); 157 } 158 }else{ 159 compileErrors.append(CompileError(tokens.getTokenLine(index), "enum name expected")); 160 } 161 } 162 return enumNode; 163 } 164 /// generates AST for a function definition 165 /// 166 /// changes `index` to the token after function definition 167 FunctionNode generateFunctionAST(){ 168 FunctionNode functionNode; 169 functionNode.lineno = tokens.getTokenLine(index); 170 // make sure it's a function 171 if (tokens.tokens[index].type == Token.Type.Keyword && tokens.tokens[index].token == "function"){ 172 // read the type 173 index++; 174 try{ 175 functionNode.returnType = readType(); 176 }catch(Exception e){ 177 compileErrors.append(CompileError(tokens.getTokenLine(index), e.msg)); 178 .destroy (e); 179 } 180 // make sure return type is not ref, thats bad, causes segfault 181 if (functionNode.returnType.isRef){ 182 compileErrors.append(CompileError(tokens.getTokenLine(index), "functions cannot return references")); 183 } 184 // above functionCall moves index to fName 185 // now index is at function name 186 functionNode.name = tokens.tokens[index].token; 187 index ++; 188 // now for the argument types 189 if (tokens.tokens[index].type == Token.Type.ParanthesesOpen){ 190 LinkedList!(FunctionNode.Argument) argList = new LinkedList!(FunctionNode.Argument); 191 uinteger brackEnd = tokens.tokens.tokenBracketPos(index); 192 bool commaExpected = false; 193 for (index ++; index < brackEnd; index ++){ 194 if (commaExpected){ 195 if (tokens.tokens[index].type == Token.Type.Comma){ 196 commaExpected = false; 197 continue; 198 }else{ 199 compileErrors.append(CompileError(tokens.getTokenLine(index), 200 "arguments must be separated using a comma")); 201 } 202 }else{ 203 // data type + arg_name expected 204 // read the type 205 FunctionNode.Argument arg; 206 try{ 207 arg.argType = readType(); 208 }catch(Exception e){ 209 compileErrors.append(CompileError(tokens.getTokenLine(index), e.msg)); 210 .destroy (e); 211 } 212 // the type must not be void, script-defined functions must all be static typed 213 if (arg.argType.type == DataType.Type.Void){ 214 compileErrors.append(CompileError(tokens.getTokenLine(index), 215 "script-defined functions can not receive arguments of type void")); 216 } 217 // now the arg_name 218 arg.argName = tokens.tokens[index].token; 219 commaExpected = true; 220 // add it to list 221 argList.append (arg); 222 } 223 } 224 // put args in list 225 functionNode.arguments = argList.toArray; 226 .destroy(argList); 227 index = brackEnd+1; 228 } 229 if (tokens.tokens[index].type == Token.Type.BlockStart){ 230 // no args, just body 231 functionNode.bodyBlock = generateBlockAST(); 232 // if there was a semicolon after function definition, skip that too 233 if (index +1 < tokens.tokens.length && tokens.tokens[index].type == Token.Type.StatementEnd) 234 index ++; 235 }else{ 236 compileErrors.append(CompileError(tokens.getTokenLine(index), "function has no body")); 237 } 238 }else{ 239 compileErrors.append(CompileError(tokens.getTokenLine(index), "not a function definition")); 240 index ++; 241 } 242 return functionNode; 243 } 244 245 /// generates AST for a {block-of-code} 246 /// 247 /// changes `index` to token after block end 248 BlockNode generateBlockAST(){ 249 BlockNode blockNode; 250 blockNode.lineno = tokens.getTokenLine(index); 251 // make sure it's a block 252 if (tokens.tokens[index].type == Token.Type.BlockStart){ 253 uinteger brackEnd = tokens.tokens.tokenBracketPos!(true)(index); 254 LinkedList!StatementNode statements = new LinkedList!StatementNode; 255 // read statements 256 index++; 257 uinteger errorCount = compileErrors.count; 258 while (index < brackEnd){ 259 statements.append(generateStatementAST()); 260 // check if error added 261 if (errorCount != compileErrors.count){ 262 break; 263 } 264 } 265 // put them all in block 266 blockNode.statements = statements.toArray; 267 .destroy(statements); 268 index = brackEnd+1; 269 }else{ 270 compileErrors.append(CompileError(tokens.getTokenLine(index), "not a block")); 271 index ++; 272 } 273 return blockNode; 274 } 275 276 /// generates AST for one statement 277 StatementNode generateStatementAST(){ 278 // identify the type, call the appropriate function 279 if (tokens.tokens[index].type == Token.Type.BlockStart){ 280 // block 281 return StatementNode(generateBlockAST()); 282 }else if (tokens.tokens[index].type == Token.Type.Keyword){ 283 if (tokens.tokens[index].token == "if"){ 284 // if statement 285 return StatementNode(generateIfAST()); 286 }else if (tokens.tokens[index].token == "while"){ 287 // while statement 288 return StatementNode(generateWhileAST()); 289 }else if (tokens.tokens[index].token == "for"){ 290 // for statement 291 return StatementNode(generateForAST()); 292 }else if (tokens.tokens[index].token == "do"){ 293 // do while 294 return StatementNode(generateDoWhileAST()); 295 }else if (tokens.tokens[index].token == "return"){ 296 // return statement 297 return StatementNode(generateReturnAST()); 298 }else if (tokens.tokens[index].token == "var"){ 299 // var declare 300 return StatementNode(generateVarDeclareAST()); 301 } 302 }else if (tokens.tokens[index].type == Token.Type.Identifier && 303 tokens.tokens[index+1].type == Token.Type.ParanthesesOpen){ 304 // is a function call 305 return StatementNode(generateFunctionCallAST()); 306 }else{ 307 // could be an assignment statement, could be something.something.something(); function call 308 // use generateCodeAST to read lvalue, or in case of later, will read whole thing 309 CodeNode lvalue = generateCodeAST(); 310 if (tokens.tokens[index].type == Token.Type.StatementEnd){ 311 // just a something.something() function call, but still, make sure 312 if (lvalue.type == CodeNode.Type.FunctionCall){ 313 index ++; 314 return StatementNode(lvalue.node!(CodeNode.Type.FunctionCall)); 315 } 316 }else if (tokens.tokens[index].type == Token.Type.AssignmentOperator){ 317 return StatementNode(generateAssignmentAST(lvalue)); 318 } 319 } 320 compileErrors.append (CompileError(tokens.getTokenLine(index), "invalid statement")); 321 index ++; 322 return StatementNode(); 323 } 324 325 /// generates AST for function call, changes `index` to token after statementEnd 326 FunctionCallNode generateFunctionCallAST(){ 327 FunctionCallNode functionCallNode; 328 functionCallNode.lineno = tokens.getTokenLine(index); 329 // check if is function call 330 if (tokens.tokens[index].type == Token.Type.Identifier && 331 tokens.tokens[index + 1].type == Token.Type.ParanthesesOpen){ 332 uinteger brackEnd = tokens.tokens.tokenBracketPos(index + 1); 333 functionCallNode.fName = tokens.tokens[index].token; 334 // now for the arguments 335 index+=2; 336 if (tokens.tokens[index].type == Token.Type.ParanthesesClose){ 337 // has no args 338 functionCallNode.arguments = []; 339 }else{ 340 LinkedList!CodeNode args = new LinkedList!CodeNode; 341 for (; ; index ++){ 342 args.append(generateCodeAST()); 343 if (tokens.tokens[index].type == Token.Type.ParanthesesClose){ 344 break; 345 } 346 } 347 functionCallNode.arguments = args.toArray; 348 .destroy(args); 349 } 350 // move index to bracketend or semicolon 351 index = brackEnd+1; 352 if (tokens.tokens[index].type == Token.Type.StatementEnd){ 353 index ++; 354 } 355 return functionCallNode; 356 }else{ 357 compileErrors.append(CompileError(tokens.getTokenLine(index), "not a valid function call")); 358 index ++; 359 } 360 return functionCallNode; 361 } 362 363 /// generates AST for "actual code" like `2 + 2 - 6`. 364 /// 365 /// terminates reading when on a Token that is comma, paranthesesEnd, semicolon, index/block bracket close 366 CodeNode generateCodeAST(){ 367 CodeNode lastNode = null; 368 bool separatorExpected = false; 369 for ( ; ; index ++){ 370 Token token = tokens.tokens[index]; 371 // check if has to terminate 372 if ([Token.Type.Comma, Token.Type.IndexBracketClose, Token.Type.BlockEnd, Token.Type.ParanthesesClose, 373 Token.Type.StatementEnd, Token.Type.AssignmentOperator].hasElement(token.type)){ 374 break; 375 } 376 if (!separatorExpected){ 377 // an identifier, or literal (i.e some data) was expected 378 lastNode = generateNodeAST(); 379 index --; 380 separatorExpected = true; 381 }else{ 382 // an operator or something that deals with data was expected. 383 // check if there's an [...] to read the array 384 if (token.type == Token.Type.IndexBracketOpen){ 385 // read the index 386 uinteger brackStartIndex = index; 387 uinteger brackEnd = tokens.tokens.tokenBracketPos(index); 388 index ++; 389 CodeNode indexNode = generateCodeAST(); 390 // make sure it's a read-element, not a make-array 391 if (index != brackEnd){ 392 compileErrors.append (CompileError(tokens.getTokenLine(index), "unexpected tokens")); 393 index++; 394 break; 395 } 396 lastNode = CodeNode(ReadElement(lastNode, indexNode)); 397 lastNode.lineno = tokens.getTokenLine(brackStartIndex); 398 continue; 399 } 400 separatorExpected = false; 401 if (token.type == Token.Type.Operator){ 402 lastNode = CodeNode(generateOperatorAST(lastNode)); 403 index --; 404 // in case of operators, generateOperatorAST takes the 2nd operand (!separator), itself, so the next 405 // token, if any, is to be a separator 406 separatorExpected = true; 407 continue; 408 }else{ 409 compileErrors.append(CompileError(tokens.getTokenLine(index), "Unexpected token '"~token.token~'\'')); 410 break; 411 } 412 } 413 } 414 return lastNode; 415 } 416 417 /// generates AST for `-x` where x is a codenode 418 NegativeValueNode generateNegativeNode(){ 419 if (tokens.tokens[index].type != Token.Type.Operator || tokens.tokens[index].token != "-"){ 420 compileErrors.append(CompileError(tokens.getTokenLine(index), "Not a negative node")); 421 index ++; 422 return NegativeValueNode(CodeNode()); 423 } 424 NegativeValueNode r; 425 index ++; 426 CodeNode val = generateCodeAST(); 427 r = NegativeValueNode(val); 428 return r; 429 } 430 431 /// generates AST for operators like +, -... 432 /// 433 /// `firstOperand` is the first operand for the operator. 434 /// `index` is the index of the token which is the operator 435 /// 436 /// changes `index` to the index of the token after the last token related to the operator 437 OperatorNode generateOperatorAST(CodeNode firstOperand){ 438 OperatorNode operator; 439 operator.lineno = tokens.getTokenLine(index); 440 // make sure it's an operator, and there is a second operand 441 if (tokens.tokens[index].type == Token.Type.Operator && OPERATORS.hasElement(tokens.tokens[index].token) && 442 index+1 < tokens.tokens.length){ 443 // read the next operand 444 CodeNode secondOperand; 445 string operatorString = tokens.tokens[index].token; 446 index ++; 447 secondOperand = generateNodeAST(); 448 // put both operands under one node 449 operator = OperatorNode(operatorString, firstOperand, secondOperand); 450 }else{ 451 compileErrors.append(CompileError(tokens.getTokenLine(index), "no second operand found")); 452 index ++; 453 } 454 return operator; 455 } 456 457 /// generates AST for single operand operators 458 SOperatorNode generateSOperatorAST(){ 459 SOperatorNode operator; 460 operator.lineno = tokens.getTokenLine(index); 461 // make sure its a single operand operator 462 if (tokens.tokens[index].type == Token.Type.Operator && SOPERATORS.hasElement(tokens.tokens[index].token)){ 463 // read the operator 464 operator.operator = tokens.tokens[index].token; 465 index++; 466 // and the operand 467 operator.operand = generateNodeAST(); 468 }else{ 469 compileErrors.append(CompileError(tokens.getTokenLine(index), "not an operator")); 470 } 471 return operator; 472 } 473 /// generates AST for a variable (or array) and changes value of index to the token after variable ends 474 VariableNode generateVariableAST(){ 475 VariableNode var; 476 var.lineno = tokens.getTokenLine(index); 477 // make sure first token is identifier 478 if (tokens.tokens[index].type == Token.Type.Identifier){ 479 // set var name 480 var.varName = tokens.tokens[index].token; 481 index ++; 482 }else{ 483 compileErrors.append(CompileError(tokens.getTokenLine(index), "not a variable")); 484 index ++; 485 } 486 return var; 487 } 488 489 /// generates AST for assignment operator 490 AssignmentNode generateAssignmentAST(CodeNode lvalue){ 491 AssignmentNode assignment; 492 assignment.lineno = tokens.getTokenLine(index); 493 // if lvalue is being read through a @ operator, mark assignment.deref as true 494 if (lvalue.type == CodeNode.Type.SOperator){ 495 SOperatorNode opNode = lvalue.node!(CodeNode.Type.SOperator); 496 if (opNode.operator == "@"){ 497 assignment.deref = true; 498 lvalue = opNode.operand; 499 } 500 } 501 // now at index, the token should be a `=` operator 502 if (tokens.tokens[index].type == Token.Type.AssignmentOperator){ 503 // everything's ok till the `=` operator 504 index++; 505 CodeNode rvalue = generateCodeAST(); 506 assignment.lvalue = lvalue; 507 assignment.rvalue = rvalue; 508 // make sure it's followed by a semicolon 509 if (tokens.tokens[index].type != Token.Type.StatementEnd){ 510 compileErrors.append(CompileError(tokens.getTokenLine(index), 511 "assingment statement not followed by semicolon")); 512 }else{ 513 // skip the semicolon too 514 index++; 515 } 516 }else{ 517 compileErrors.append(CompileError(tokens.getTokenLine(index), "not an assignment statement")); 518 index ++; 519 } 520 return assignment; 521 } 522 523 /// generates AST for variable declarations 524 VarDeclareNode generateVarDeclareAST(){ 525 VarDeclareNode varDeclare; 526 varDeclare.lineno = tokens.getTokenLine(index); 527 // make sure it's a var declaration, and the vars are enclosed in parantheses 528 if (tokens.tokens[index] == Token(Token.Type.Keyword, "var")){ 529 index ++; 530 // read the type 531 try{ 532 varDeclare.type = readType(); 533 if (!AVAILABLE_DATA_TYPES.hasElement(varDeclare.type.type) && DATA_TYPES.hasElement(varDeclare.type.typeName)) 534 throw new Exception("Data type "~varDeclare.type.typeName~" is currently not supported"); 535 }catch(Exception e){ 536 compileErrors.append(CompileError(tokens.getTokenLine(index), e.msg)); 537 .destroy (e); 538 } 539 while (tokens.tokens[index].type != Token.Type.StatementEnd && index < tokens.tokens.length){ 540 if (tokens.tokens[index].type == Token.Type.Identifier){ 541 string varName = tokens.tokens[index].token; 542 // see if it has a assigned value, case not, skip to next 543 if (tokens.tokens[index+1].type == Token.Type.AssignmentOperator){ 544 index += 2; 545 CodeNode value = generateCodeAST(); 546 varDeclare.addVar(varName, value); 547 }else{ 548 varDeclare.addVar(varName); 549 index ++; 550 } 551 // now there must be a comma 552 if (tokens.tokens[index].type == Token.Type.Comma){ 553 index ++; 554 }else if (tokens.tokens[index].type != Token.Type.StatementEnd){ 555 compileErrors.append (CompileError(tokens.getTokenLine(index), 556 "variable names must be separated by a comma")); 557 } 558 continue; 559 }else{ 560 compileErrors.append (CompileError(tokens.getTokenLine(index), "variable name expected")); 561 // jump to end of statement 562 while (tokens.tokens[index].type != Token.Type.StatementEnd && index < tokens.tokens.length){ 563 index ++; 564 } 565 break; 566 } 567 } 568 // skip the semicolon 569 if (tokens.tokens[index].type == Token.Type.StatementEnd){ 570 index ++; 571 } 572 }else{ 573 compileErrors.append(CompileError(tokens.getTokenLine(index), "invalid variable declaration")); 574 index ++; 575 } 576 return varDeclare; 577 } 578 579 /// generates AST for if statements 580 IfNode generateIfAST(){ 581 IfNode ifNode; 582 ifNode.lineno = tokens.getTokenLine(index); 583 // check if is an if 584 if (tokens.tokens[index].type == Token.Type.Keyword && tokens.tokens[index].token == "if" && 585 tokens.tokens[index+1].type == Token.Type.ParanthesesOpen){ 586 // now do the real work 587 uinteger brackEnd = tokens.tokens.tokenBracketPos(index+1); 588 index += 2; 589 ifNode.condition = generateCodeAST(); 590 // make sure index & brackEnd are now same 591 if (index == brackEnd){ 592 index = brackEnd+1; 593 ifNode.statement = generateStatementAST(); 594 ifNode.hasElse = false; 595 // check if there's any else statement 596 if (tokens.tokens[index].type == Token.Type.Keyword && tokens.tokens[index].token == "else"){ 597 // add that as well 598 index ++; 599 ifNode.elseStatement = generateStatementAST(); 600 ifNode.hasElse = true; 601 } 602 }else{ 603 compileErrors.append(CompileError(tokens.getTokenLine(index), "syntax error in condition")); 604 } 605 }else{ 606 compileErrors.append(CompileError(tokens.getTokenLine(index), "not a valid if/while statement")); 607 index ++; 608 } 609 return ifNode; 610 } 611 612 /// generates AST for while statements 613 WhileNode generateWhileAST(){ 614 WhileNode whileNode; 615 whileNode.lineno = tokens.getTokenLine(index); 616 // check if is an if 617 if (tokens.tokens[index].type == Token.Type.Keyword && tokens.tokens[index].token == "while" && 618 tokens.tokens[index+1].type == Token.Type.ParanthesesOpen){ 619 // now do the real work 620 uinteger brackEnd = tokens.tokens.tokenBracketPos!true(index+1); 621 index += 2; 622 whileNode.condition = generateCodeAST(); 623 // skip the brackEnd, if index matches it 624 if (index == brackEnd){ 625 index++; 626 whileNode.statement = generateStatementAST(); 627 }else{ 628 compileErrors.append(CompileError(tokens.getTokenLine(index), "syntax error in condition")); 629 } 630 }else{ 631 compileErrors.append(CompileError(tokens.getTokenLine(index), "not a valid if/while statement")); 632 index ++; 633 } 634 return whileNode; 635 } 636 637 /// generates AST for for loop statements 638 ForNode generateForAST(){ 639 ForNode forNode; 640 forNode.lineno = tokens.getTokenLine(index); 641 // check if is a for statement 642 if (tokens.tokens[index] == Token(Token.Type.Keyword, "for") && tokens.tokens[index+1].type == Token.Type.ParanthesesOpen){ 643 /// where the parantheses ends 644 uinteger bracketEnd = tokens.tokens.tokenBracketPos(index+1); 645 /// get the init statement 646 index = index + 2; 647 forNode.initStatement = generateStatementAST(); 648 /// get the condition 649 forNode.condition = generateCodeAST(); 650 /// make sure there's a semicolon 651 if (tokens.tokens[index].type != Token.Type.StatementEnd){ 652 compileErrors.append(CompileError(tokens.getTokenLine(index), "semicolon expected after for loop condition")); 653 } 654 index ++; 655 /// get the increment statement 656 forNode.incStatement = generateStatementAST(); 657 if (index == bracketEnd){ 658 /// now for the for loop body 659 index ++; 660 forNode.statement = generateStatementAST(); 661 }else{ 662 compileErrors.append(CompileError(tokens.getTokenLine(index), "closing parantheses expected after statement")); 663 } 664 } 665 return forNode; 666 } 667 668 /// generates AST for do-while loops statements 669 DoWhileNode generateDoWhileAST(){ 670 DoWhileNode doWhile; 671 doWhile.lineno = tokens.getTokenLine(index); 672 if (tokens.tokens[index] == Token(Token.Type.Keyword, "do")){ 673 // get the statement 674 index ++; 675 doWhile.statement = generateStatementAST(); 676 // now make sure there's a while there 677 if (tokens.tokens[index] == Token(Token.Type.Keyword, "while") && 678 tokens.tokens[index+1].type == Token.Type.ParanthesesOpen){ 679 // read the condition 680 uinteger brackEnd = tokens.tokens.tokenBracketPos(index+1); 681 index += 2; 682 doWhile.condition = generateCodeAST(); 683 if (index != brackEnd){ 684 compileErrors.append (CompileError(tokens.getTokenLine(index), "syntax error")); 685 }else{ 686 // skip the bracket end 687 index ++; 688 // the semicolon after `do ... while(...)` is optional, so if it's there, skip it 689 if (tokens.tokens[index].type == Token.Type.StatementEnd){ 690 index ++; 691 } 692 } 693 }else{ 694 compileErrors.append (CompileError(tokens.getTokenLine(index), 695 "while followed by condition expected after end of loop statement")); 696 } 697 } 698 return doWhile; 699 } 700 701 /// returns a node representing a return statement 702 ReturnNode generateReturnAST(){ 703 if (tokens.tokens[index].token != "return"){ 704 compileErrors.append(CompileError(tokens.getTokenLine(index), "not a return statement")); 705 }else{ 706 ReturnNode r; 707 r.lineno = tokens.getTokenLine(index); 708 index ++; 709 r.value = generateCodeAST(); 710 if (tokens.tokens[index].type == Token.Type.StatementEnd){ 711 index ++; //skip semicolon 712 }else{ 713 compileErrors.append(CompileError(tokens.getTokenLine(index),"semicolon expected")); 714 } 715 return r; 716 } 717 return ReturnNode(); 718 } 719 720 /// returns a node representing either of the following: 721 /// 1. String literal 722 /// 2. Number literal 723 /// 3. Function Call (uses `generateFunctionCallAST`) 724 /// 4. Variable (uses `generateVariableAST`) 725 /// 5. Some code inside parantheses (uses `generateCodeAST`) 726 /// 6. A literal array (`[x, y, z]`) 727 /// 728 /// This function is used by `generateCodeAST` to separate nodes, and by `generateOperatorAST` to read operands 729 /// 730 /// set `skipPost` to true in case you do not want it to read into `[...]` or `.` after the Node 731 CodeNode generateNodeAST(CodeNode r = CodeNode()){ 732 Token token = tokens.tokens[index]; 733 // an identifier, or literal (i.e some data) was expected 734 if (token.type == Token.Type.Identifier){ 735 if (index+1 < tokens.tokens.length && tokens.tokens[index+1].type == Token.Type.ParanthesesOpen){ 736 // is a function call 737 r = CodeNode(generateFunctionCallAST()); 738 // check if it skipped the semicolon, because if it did, it shouldnt have, so undo it 739 if (tokens.tokens[index-1].type == Token.Type.StatementEnd){ 740 index --; 741 } 742 }else{ 743 // just a var 744 r = CodeNode(generateVariableAST()); 745 } 746 }else if (token.type == Token.Type.Operator && SOPERATORS.hasElement(token.token)){ 747 r = CodeNode(generateSOperatorAST()); 748 }else if (token.type == Token.Type.Operator && token.token == "-"){ 749 r = CodeNode(generateNegativeNode()); 750 }else if (token.type == Token.Type.ParanthesesOpen){ 751 // some code 752 index ++; 753 r = generateCodeAST(); 754 index ++; 755 }else if (token.type == Token.Type.IndexBracketOpen){ 756 // literal array 757 uinteger brackEnd = tokens.tokens.tokenBracketPos(index); 758 // read into ArrayNode 759 CodeNode[] elements = []; 760 index ++; 761 for (; index < brackEnd; index ++){ 762 elements = elements ~ generateCodeAST(); 763 if (tokens.tokens[index].type != Token.Type.Comma && index != brackEnd){ 764 compileErrors.append (CompileError (tokens.getTokenLine(index), 765 "Unexpected token, comma must be used to separate array elements")); 766 index = brackEnd; 767 } 768 } 769 index = brackEnd+1; 770 r = CodeNode(ArrayNode(elements)); 771 }else if (token.type == Token.Type.Double || token.type == Token.Type.Integer || token.type == Token.Type.String || 772 token.type == Token.Type.Char || token.type == Token.Type.Bool){ 773 // literal 774 index ++; 775 try{ 776 LiteralNode ltNode = LiteralNode(token); 777 ltNode.lineno = tokens.getTokenLine(index); 778 r = CodeNode(ltNode); 779 }catch (Exception e){ 780 compileErrors.append(CompileError(tokens.getTokenLine(index), e.msg)); 781 .destroy (e); 782 } 783 }else if (token.type == Token.Type.Keyword && token.token == "null"){ 784 r = CodeNode(LiteralNode(token.token,DataType(DataType.Type.Void, 0, true))); 785 index ++; 786 }else{ 787 index ++; 788 compileErrors.append(CompileError(tokens.getTokenLine(index), "unexpected token")); 789 } 790 // check if theres a [] or MemberSelector ahead of it to read it an element 791 while (tokens.tokens[index].type == Token.Type.IndexBracketOpen || 792 tokens.tokens[index].type == Token.Type.MemberSelector){ 793 if (tokens.tokens[index].type == Token.Type.IndexBracketOpen){ 794 // skip the opening bracket `[` 795 index ++; 796 r = CodeNode(ReadElement(r, generateCodeAST())); 797 if (tokens.tokens[index].type == Token.Type.IndexBracketClose) 798 index ++; // skip the ] bracket 799 }else if (tokens.tokens[index].type == Token.Type.MemberSelector){ 800 // read just the member name 801 immutable uinteger lineno = tokens.getTokenLine(index); 802 index ++; 803 if (tokens.tokens[index].type != Token.Type.Identifier){ 804 compileErrors.append(CompileError(tokens.getTokenLine(index), 805 "Expected identifier after member selector operator")); 806 }else{ 807 // check if is a function call 808 if (index + 1 < tokens.tokens.length && tokens.tokens[index+1].type == Token.Type.ParanthesesOpen){ 809 //index ++; 810 FunctionCallNode fCall = generateFunctionCallAST(); 811 fCall.arguments = r ~ fCall.arguments; 812 r = CodeNode(fCall); 813 if (tokens.tokens[index-1].type == Token.Type.StatementEnd){ 814 index --; 815 break; 816 } 817 } 818 r = CodeNode(MemberSelectorNode(r, tokens.tokens[index].token, lineno)); 819 index ++; 820 } 821 } 822 } 823 824 return r; 825 } 826 /// generates LiteralNode 827 LiteralNode generateLiteralAST(){ 828 LiteralNode r; 829 if ([Token.Type.Double,Token.Type.Integer,Token.Type.String,Token.Type.Char].hasElement(tokens.tokens[index].type)){ 830 try{ 831 r = LiteralNode(tokens.tokens[index]); 832 r.lineno = tokens.getTokenLine(index); 833 }catch (Exception e){ 834 compileErrors.append(CompileError(tokens.getTokenLine(index), e.msg)); 835 .destroy(e); 836 } 837 }else{ 838 compileErrors.append(CompileError(tokens.getTokenLine(index), "literal expected")); 839 } 840 index ++; 841 return r; 842 } 843 public: 844 /// Constructor 845 this(){ 846 compileErrors = new LinkedList!CompileError; 847 } 848 ~this(){ 849 .destroy(compileErrors); 850 } 851 /// Returns: errors occured during AST generation 852 @property CompileError[] errors(){ 853 return compileErrors.toArray(); 854 } 855 /// generates an AST representing a script. 856 ScriptNode generateScriptAST(TokenList scriptTokens){ 857 tokens = scriptTokens; 858 tokens.tokens = tokens.tokens.dup; 859 tokens.tokenPerLine = tokens.tokenPerLine.dup; 860 ScriptNode scriptNode; 861 compileErrors.clear(); 862 // go through the script, compile function nodes, link them to this node 863 index = 0; 864 for (immutable uinteger lastIndex = tokens.tokens.length - 1; index < tokens.tokens.length; index ++){ 865 // look for visibility specifier, or default to private 866 Visibility vis = Visibility.Private; 867 if (tokens.tokens[index].type == Token.Type.Keyword && VISIBILITY_SPECIFIERS.hasElement(tokens.tokens[index].token)){ 868 vis = visibility(tokens.tokens[index].token); 869 index ++; 870 } 871 // look for TokenType.Keyword where token.token == "function" 872 if (tokens.tokens[index].type == Token.Type.Keyword){ 873 immutable uinteger errorCount = compileErrors.count; 874 if (tokens.tokens[index].token == "import"){ 875 string[] imports = readImports(); 876 scriptNode.imports ~= imports; 877 index --; // don't wanna skip the semicolon, for (..; index ++) does that 878 }else if (tokens.tokens[index].token == "function"){ 879 FunctionNode functionNode = generateFunctionAST(); 880 functionNode.visibility = vis; 881 index --; 882 // check if error free 883 if (compileErrors.count == errorCount){ 884 scriptNode.functions ~= functionNode; 885 } 886 }else if (tokens.tokens[index].token == "struct"){ 887 StructNode structNode = generateStructAST(); 888 structNode.visibility = vis; 889 index --; 890 if (compileErrors.count == errorCount){ 891 scriptNode.structs ~= structNode; 892 } 893 }else if (tokens.tokens[index].token == "enum"){ 894 EnumNode enumNode = generateEnumAST(); 895 enumNode.visibility = vis; 896 index --; 897 if (compileErrors.count == errorCount){ 898 scriptNode.enums ~= enumNode; 899 } 900 }else if (tokens.tokens[index].token == "var"){ 901 VarDeclareNode varDecNode = generateVarDeclareAST(); 902 varDecNode.visibility = vis; 903 index --; 904 if (compileErrors.count == errorCount){ 905 scriptNode.variables ~= varDecNode; 906 } 907 } 908 } 909 } 910 return scriptNode; 911 } 912 }