1 /++ 2 All the compiler modules packaged into one, this one module should be used to compile scripts. 3 +/ 4 module qscript.compiler.compiler; 5 6 import qscript.compiler.tokengen; 7 import qscript.compiler.ast; 8 import qscript.compiler.astgen; 9 import qscript.compiler.astcheck; 10 import qscript.compiler.codegen; 11 import qscript.compiler.astreadable; 12 import qscript.qscript : QScriptBytecode; 13 14 import navm.bytecode; 15 16 import std.json; 17 import std.range; 18 import std.traits; 19 import std.conv : to; 20 21 import utils.misc; 22 23 /// An array containing all chars that an identifier can contain 24 package const char[] IDENT_CHARS = iota('a', 'z'+1).array~iota('A', 'Z'+1).array~iota('0', '9'+1).array~[cast(int)'_']; 25 /// An array containing all keywords 26 package const string[] KEYWORDS = [ 27 "import", 28 "function", 29 "struct", 30 "enum", 31 "var", 32 "return", 33 "if", 34 "else", 35 "while", 36 "for", 37 "do", 38 "null", 39 "true", 40 "false" 41 ] ~ DATA_TYPES ~ VISIBILITY_SPECIFIERS; 42 /// Visibility Specifier keywords 43 package const string[] VISIBILITY_SPECIFIERS = ["public", "private"]; 44 /// data types 45 package const string[] DATA_TYPES = ["void", "int", "double", "char", "bool"]; 46 /// An array containing double-operand operators 47 package const string[] OPERATORS = [".", "/", "*", "+", "-", "%", "~", "<", ">", ">=", "<=", "==", "=", "&&", "||"]; 48 /// single-operand operators 49 package const string[] SOPERATORS = ["!", "@"]; 50 /// An array containing all bool-operators (operators that return true/false) 51 package const string[] BOOL_OPERATORS = ["<", ">", ">=", "<=", "==", "&&", "||"]; 52 /// function names corresponding to operators 53 package string[string] OPERATOR_FUNCTIONS; 54 static this(){ 55 OPERATOR_FUNCTIONS = [ 56 "." : "opMemberSelect", 57 "/" : "opDivide", 58 "*" : "opMultiply", 59 "+" : "opAdd", 60 "-" : "opSubtract", 61 "%" : "opMod", 62 "~" : "opConcat", 63 "<" : "opLesser", 64 ">" : "opGreater", 65 "<=" : "opLesserSame", 66 ">=" : "opGreaterSame", 67 "==" : "opSame", 68 "=" : "opAssign", 69 "&&" : "opAndBool", 70 "||" : "opOrBool", 71 "!" : "opNot", 72 "@" : "opRef", 73 ]; 74 } 75 /// Stores what types can be converted to what other types implicitly. 76 package const DataType.Type[][] IMPLICIT_CAST_TYPES = [ 77 [DataType.Type.Int, DataType.Type.Bool], 78 [DataType.Type.Double], 79 [DataType.Type.Char], 80 [DataType.Type.Bool], 81 [DataType.Type.Void], 82 ]; 83 /// Stores numerical data types (where numbers are stored) 84 package const DataType.Type[] NUMERICAL_DATA_TYPES = [ 85 DataType.Type.Int, 86 DataType.Type.Double, 87 ]; 88 /// only these data types are currently available 89 public const DataType.Type[] AVAILABLE_DATA_TYPES = [ 90 DataType.Type.Int, DataType.Type.Double, DataType.Type.Char 91 ]; 92 93 /// Used by compiler's functions to return error 94 public struct CompileError{ 95 string msg; /// The error stored in a string 96 uinteger lineno; /// The line number on which the error is 97 /// constructor 98 this(uinteger lineNumber, string errorMessage){ 99 lineno = lineNumber; 100 msg = errorMessage; 101 } 102 } 103 104 /// Flags passed to bytecode generating functions 105 /// 106 /// Not all functions look at each flag, some functions might ignore all flags 107 public enum CodeGenFlags : ubyte{ 108 None = 0, /// all flags zero 109 PushRef = 1 << 0, /// if the code should push a reference to the needed data. By default, the actual data is pushed. 110 PushFunctionReturn = 1 << 1, /// if the return value from FunctionCallNode should be pushed 111 } 112 113 /// Visibility Specifiers 114 package enum Visibility{ 115 Public, 116 Private 117 } 118 119 /// Returns: Visibilty from a string 120 /// 121 /// Throws: Exception if invalid input provided 122 package Visibility visibility(string s){ 123 foreach (curVisibility; EnumMembers!Visibility){ 124 if (curVisibility.to!string.lowercase == s) 125 return curVisibility; 126 } 127 throw new Exception(s~" is not a visibility option"); 128 } 129 130 /// Checks if a type can be implicitly casted to another type. does not work for custom types, returns false 131 /// 132 /// Returns: true if can cast implicitely 133 public bool canImplicitCast(DataType.Type type1, DataType.Type type2){ 134 if (type1 == DataType.Type.Custom || type2 == DataType.Type.Custom) 135 return false; 136 foreach(list; IMPLICIT_CAST_TYPES){ 137 if (list.hasElement(type1) && list.hasElement(type2)) 138 return true; 139 } 140 return false; 141 } 142 /// ditto 143 public bool canImplicitCast(DataType type1, DataType type2){ 144 if (type1.arrayDimensionCount == type1.arrayDimensionCount && type1.isRef == type2.isRef){ 145 if (type1.type == DataType.Type.Custom || type2.type == DataType.Type.Custom) 146 return type1.typeName == type2.typeName; 147 return canImplicitCast(type1.type, type2.type); 148 } 149 return false; 150 } 151 152 /// comma separates a string. 153 /// 154 /// Returns: the comma separated string 155 public string[] commaSeparate(char comma=',', bool excludeEmpty=false)(string str){ 156 string[] r; 157 uinteger readFrom; 158 for(uinteger i=0; i < str.length ; i ++){ 159 if (str[i] == comma || i+1 == str.length){ 160 if (readFrom < i) 161 r ~= str[readFrom .. i]; 162 else static if (!excludeEmpty) 163 r ~= ""; 164 readFrom = i+1; 165 } 166 } 167 return r; 168 } 169 170 public import qscript.qscript : Library; 171 172 /// Function called to generate bytecode for a call to a macro function 173 public alias FunctionCallCodeGenFunc = void delegate (string name, DataType[] argTypes, NaBytecode bytecode); 174 /// Function called to generate bytecode for accessing a macro variable 175 public alias VariableCodeGenFunc = void delegate (string name, NaBytecode bytecode); 176 /// To store information about a function 177 public struct Function{ 178 /// the name of the function 179 string name; 180 /// the data type of the value returned by this function 181 DataType returnType; 182 /// stores the data type of the arguments received by this function 183 private DataType[] _argTypes; 184 /// the data type of the arguments received by this function 185 @property ref DataType[] argTypes() return{ 186 return _argTypes; 187 } 188 /// the data type of the arguments received by this function 189 @property ref DataType[] argTypes(DataType[] newArray) return{ 190 return _argTypes = newArray.dup; 191 } 192 /// constructor 193 this (string functionName, DataType functionReturnType, DataType[] functionArgTypes){ 194 name = functionName; 195 returnType = functionReturnType; 196 _argTypes = functionArgTypes.dup; 197 } 198 /// constructor (reads from string generated by Function.toString) 199 /// 200 /// Throws: Exception in case of error 201 this (string functionString){ 202 string err = this.fromString(functionString); 203 if (err.length) 204 throw new Exception(err); 205 } 206 /// postblit 207 this (this){ 208 this._argTypes = this._argTypes.dup; 209 } 210 /// Returns: a string representation of this Function 211 string toString(){ 212 char[] r = cast(char[])"func,"~name~','~returnType.toString~','; 213 foreach (type; _argTypes) 214 r ~= type.toString~','; 215 return cast(string)r; 216 } 217 /// Reads this Function from a string (reverse of toString) 218 /// 219 /// Returns: zero length string if success, or error description in string if error 220 string fromString(string str){ 221 if (str.length == 0) 222 return "cannot read Function from empty string"; 223 string[] vals = commaSeparate(str); 224 if (vals.length < 3 || vals[0] != "func") 225 return "invalid string to read Function from"; 226 name = vals[1]; 227 try 228 returnType = DataType(vals[2]); 229 catch (Exception e){ 230 string r = e.msg; 231 .destroy(e); 232 return "error reading Function returnType: "~r; 233 } 234 _argTypes = []; 235 if (vals.length > 3){ 236 _argTypes.length = vals.length - 3; 237 foreach(i, arg; vals[3 .. $]){ 238 try 239 _argTypes[i] = DataType(arg); 240 catch (Exception e){ 241 string r = e.msg; 242 .destroy(e); 243 return "error reading Function arguments: "~r; 244 } 245 } 246 } 247 return []; 248 } 249 } 250 /// 251 unittest{ 252 Function func = Function("potato",DataType(DataType.Type.Void),[]); 253 assert(Function(func.toString) == func, func.toString); 254 func = Function("potato",DataType(DataType.Type.Int),[DataType("@potatoType[]")]); 255 assert(Function(func.toString) == func, func.toString); 256 } 257 /// To store information about a struct 258 public struct Struct{ 259 /// the name of this struct 260 string name; 261 /// name of members of this struct 262 private string[] _membersName; 263 /// ditto 264 @property ref string[] membersName() return{ 265 return _membersName; 266 } 267 /// ditto 268 @property ref string[] membersName(string[] newVal) return{ 269 return _membersName = newVal; 270 } 271 /// data types of members of this struct 272 private DataType[] _membersDataType; 273 /// ditto 274 @property ref DataType[] membersDataType() return{ 275 return _membersDataType; 276 } 277 /// ditto 278 @property ref DataType[] membersDataType(DataType[] newVal) return{ 279 return _membersDataType = newVal; 280 } 281 /// postblit 282 this (this){ 283 this._membersName = this._membersName.dup; 284 this._membersDataType = this._membersDataType.dup; 285 } 286 /// constructor, reads from string (uses fromString) 287 /// 288 /// Throws: Exception in case of error 289 this(string structString){ 290 string err = fromString(structString); 291 if (err.length) 292 throw new Exception(err); 293 } 294 /// constructor 295 this (string name, string[] members, DataType[] dTypes){ 296 this.name = name; 297 _membersName = members.dup; 298 _membersDataType = dTypes.dup; 299 } 300 /// Returns: a string representation of this Struct 301 string toString(){ 302 char[] r = cast(char[])"struct,"~name~','; 303 foreach (memberName; _membersName) 304 r ~= memberName ~ ','; 305 foreach (type; _membersDataType) 306 r~= type.toString ~ ','; 307 return cast(string)r; 308 } 309 /// Reads this Struct from a string (reverse of toString) 310 /// 311 /// Returns: empty string if success, or error in string in case of error 312 string fromString(string str){ 313 string[] vals = commaSeparate(str); 314 if (vals.length == 0 || vals[0] != "struct" || vals.length % 2 > 0) 315 return "invalid string to read Struct from"; 316 name = vals[1]; 317 vals = vals[2 .. $]; 318 immutable uinteger dTypeStartIndex = vals.length/2; 319 _membersDataType.length = dTypeStartIndex; 320 _membersName.length = _membersDataType.length; 321 foreach (i,val; vals[0 .. dTypeStartIndex]){ 322 _membersName[i] = val; 323 try 324 _membersDataType[i] = DataType(vals[dTypeStartIndex+i]); 325 catch (Exception e){ 326 string r = e.msg; 327 .destroy(e); 328 return "error reading Struct member data type: " ~ r; 329 } 330 } 331 return []; 332 } 333 } 334 /// 335 unittest{ 336 Struct str = Struct("potatoStruct",["a","b","c"],[DataType("int"),DataType("@double[]"),DataType("@char[]")]); 337 assert(Struct(str.toString) == str); 338 } 339 /// To store information about a enum 340 public struct Enum{ 341 /// name of the enum 342 string name; 343 /// members names, index is their value 344 private string[] _members; 345 /// ditto 346 @property ref string[] members() return{ 347 return _members; 348 } 349 /// ditto 350 @property ref string[] members(string[] newVal) return{ 351 return _members = newVal; 352 } 353 /// postblit 354 this (this){ 355 this._members = this._members.dup; 356 } 357 /// Constructor 358 this (string name, string[] members){ 359 this.name = name; 360 this._members = members.dup; 361 } 362 /// Constructor, reads from string (uses fromString) 363 this (string enumString){ 364 fromString(enumString); 365 } 366 /// Returns: string representation of this enum 367 string toString(){ 368 char[] r = cast(char[])"enum,"~name~','; 369 foreach (member; _members) 370 r ~= member ~ ','; 371 return cast(string)r; 372 } 373 /// Reads this Enum from string (reverse of toString) 374 /// 375 /// Returns: empty string in case of success, error in string in case of error 376 string fromString(string str){ 377 string[] vals = commaSeparate(str); 378 if (vals.length == 0 || vals[0] != "enum") 379 return "invalid string to read Enum from"; 380 name = vals[1]; 381 vals = vals[2 .. $]; 382 _members = vals.dup; // ez 383 return []; 384 } 385 } 386 /// To store information about a global variable 387 public struct Variable{ 388 /// name of var 389 string name; 390 /// data type 391 DataType type; 392 /// constructor 393 this(string name, DataType type){ 394 this.name = name; 395 this.type = type; 396 } 397 /// Constructor, for reading from string (uses fromString) 398 /// 399 /// Throws: Exception in case of error 400 this(string varString){ 401 fromString(varString); 402 } 403 /// Returns: a string representation of this Variable 404 string toString(){ 405 return "var,"~name~','~type.toString~','; 406 } 407 /// Reads this Variable from a string (reverse of toString) 408 /// 409 /// Returns: empty string in case of success, or error in string in case of error 410 string fromString(string str){ 411 string[] vals = commaSeparate(str); 412 if (vals.length != 3 || vals[0] != "var") 413 return "invalid string to read Variable from"; 414 name = vals[1]; 415 try 416 type = DataType(vals[2]); 417 catch (Exception e){ 418 string r = e.msg; 419 .destroy (e); 420 return "error reading Variable data type: " ~ r; 421 } 422 return []; 423 } 424 } 425 /// 426 unittest{ 427 Variable var = Variable("i",DataType("@int[][]")); 428 assert (Variable(var.toString) == var); 429 } 430 431 /// used to store data types for data at compile time 432 public struct DataType{ 433 /// enum defining all data types. These are all lowercase of what they're written here 434 public enum Type{ 435 Void, /// . 436 Char, /// . 437 Int, /// . 438 Double, /// . 439 Bool, /// . 440 Custom, /// some other type 441 } 442 /// the actual data type 443 Type type = DataType.Type.Void; 444 /// stores if it's an array. If type is `int`, it will be 0, if `int[]` it will be 1, if `int[][]`, then 2 ... 445 uinteger arrayDimensionCount = 0; 446 /// stores length in case of Type.Custom 447 uinteger customLength; 448 /// stores if it's a reference to a type 449 bool isRef = false; 450 /// stores the type name in case of Type.Custom 451 private string _name; 452 /// Returns: this data type in human readable string. 453 @property string name(){ 454 char[] r = cast(char[])(this.type == Type.Custom ? _name.dup : this.type.to!string.lowercase); 455 if (this.isRef) 456 r = '@' ~ r; 457 uinteger i = r.length; 458 if (this.isArray){ 459 r.length += this.arrayDimensionCount * 2; 460 for (; i < r.length; i += 2){ 461 r[i .. i + 2] = "[]"; 462 } 463 } 464 return cast(string)r; 465 } 466 /// Just calls fromString() 467 @property string name(string newName){ 468 this.fromString(newName); 469 return newName; 470 } 471 /// Returns: only the type name, exlcuding [] or @ if present 472 @property string typeName(){ 473 return type == Type.Custom ? _name : type.to!string; 474 } 475 /// Returns: true if the type is a custom one 476 @property bool isCustom(){ 477 return type == Type.Custom; 478 } 479 /// returns: true if it's an array. Strings are arrays too (char[]) 480 @property bool isArray(){ 481 if (arrayDimensionCount > 0){ 482 return true; 483 } 484 return false; 485 } 486 /// Returns: true if arithmatic operators can be used on a data type 487 @property bool isNumerical(){ 488 if (this.isArray || this.isRef) 489 return false; 490 return NUMERICAL_DATA_TYPES.hasElement(this.type); 491 } 492 /// constructor. 493 /// 494 /// dataType is the type to store 495 /// arrayDimension is the number of nested arrays 496 /// isRef is whether the type is a reference to the actual type 497 this (DataType.Type dataType, uinteger arrayDimension = 0, bool isReference = false){ 498 type = dataType; 499 arrayDimensionCount = arrayDimension; 500 isRef = isReference; 501 } 502 /// constructor. 503 /// 504 /// dataType is the name of type to store 505 /// arrayDimension is the number of nested arrays 506 /// isRef is whether the type is a reference to the actual type 507 this (string dataType, uinteger arrayDimension = 0, bool isReference = false){ 508 this.name = dataType; 509 arrayDimensionCount = arrayDimension; 510 isRef = isReference; 511 } 512 513 /// constructor. 514 /// 515 /// `sType` is the type in string form 516 this (string sType){ 517 fromString(sType); 518 } 519 /// constructor. 520 /// 521 /// `data` is the data to infer type from 522 this (Token data){ 523 fromData(data); 524 } 525 /// reads DataType from a string, works for base types and custom types 526 /// 527 /// Throws: Exception in case of failure or bad format in string 528 void fromString(string s){ 529 isRef = false; 530 string sType = null; 531 uinteger indexCount = 0; 532 // check if it's a ref 533 if (s.length > 0 && s[0] == '@'){ 534 isRef = true; 535 s = s[1 .. s.length].dup; 536 } 537 // read the type 538 for (uinteger i = 0; i < s.length; i ++){ 539 if (s[i] == '['){ 540 sType = s[0 .. i]; 541 break; 542 }else if (i+1 == s.length){ 543 sType = s; 544 break; 545 } 546 } 547 // now read the index 548 for (uinteger i = sType.length; i < s.length; i ++){ 549 if (s[i] == '['){ 550 // make sure next char is a ']' 551 i ++; 552 if (s[i] != ']'){ 553 throw new Exception("invalid data type format"); 554 } 555 indexCount ++; 556 }else{ 557 throw new Exception("invalid data type"); 558 } 559 } 560 bool isCustom = true; 561 foreach (curType; EnumMembers!Type){ 562 if (curType != Type.Custom && curType.to!string.lowercase == sType){ 563 this.type = curType; 564 isCustom = false; 565 } 566 } 567 if (isCustom){ 568 this.type = Type.Custom; 569 this._name = sType; 570 } 571 arrayDimensionCount = indexCount; 572 } 573 574 /// identifies the data type from the actual data. Only works for base types, and with only 1 token. So arrays dont work. 575 /// keep in mind, this won't be able to identify if the data type is a reference or not 576 /// 577 /// throws Exception on failure 578 void fromData(Token data){ 579 isRef = false; 580 arrayDimensionCount = 0; 581 if (data.type == Token.Type.String){ 582 arrayDimensionCount ++; 583 type = DataType.Type.Char; 584 }else if (data.type == Token.Type.Char){ 585 type = DataType.Type.Char; 586 }else if (data.type == Token.Type.Integer){ 587 type = DataType.Type.Int; 588 }else if (data.type == Token.Type.Double){ 589 type = DataType.Type.Double; 590 }else if (data.type == Token.Type.Bool){ 591 type = DataType.Type.Bool; 592 }else if (data.type == Token.Type.Keyword && data.token == "null"){ 593 isRef = true; 594 type = DataType.Type.Void; 595 }else{ 596 throw new Exception("failed to read data type"); 597 } 598 } 599 /// Returns: human readable string of this (calls name()) 600 string toString(){ 601 return name; 602 } 603 } 604 /// 605 unittest{ 606 assert(DataType("int") == DataType(DataType.Type.Int, 0)); 607 assert(DataType("char[][]") == DataType(DataType.Type.Char, 2)); 608 assert(DataType("double[][]") == DataType(DataType.Type.Double, 2)); 609 assert(DataType("void") == DataType(DataType.Type.Void, 0)); 610 // unittests for `fromData` 611 DataType dType; 612 dType.fromData(Token("\"bla bla\"")); 613 assert(dType == DataType("char[]")); 614 dType.fromData(Token("20")); 615 assert(dType == DataType("int"), dType.name); 616 dType.fromData(Token("2.5")); 617 assert(dType == DataType("double")); 618 // unittests for `.name()` 619 assert(DataType("potatoType[][]").name == "potatoType[][]"); 620 assert(DataType("double[]").name == "double[]"); 621 } 622 623 /// all the compiler modules wrapped into a single class. This is all that should be needed to compile scripts 624 public class QSCompiler{ 625 private: 626 ASTGen _astgenerator; 627 ASTCheck _astcheck; 628 CodeGen _codegen; 629 630 CompileError[] _errors; 631 632 string[] _script; 633 TokenList _tokens; 634 ScriptNode _ast; 635 QScriptBytecode _bytecode; 636 637 Library _scriptExports; 638 Library _scriptDeclarations; 639 public: 640 /// constructor 641 this(Library[] libraries, NaInstruction[] instructionTable){ 642 _astgenerator = new ASTGen(); 643 _astcheck = new ASTCheck(libraries); 644 _codegen = new CodeGen(libraries, instructionTable); 645 } 646 /// destructor 647 ~this(){ 648 .destroy(_astgenerator); 649 .destroy(_astcheck); 650 .destroy(_codegen); 651 } 652 /// The Library to which script's exports will be written to 653 @property Library scriptExports(){ 654 return _scriptExports; 655 } 656 /// ditto 657 @property Library scriptExports(Library newVal){ 658 return _scriptExports = newVal; 659 } 660 /// what errors occurred 661 @property CompileError[] errors(){ 662 return _errors.dup; 663 } 664 /// clears errors 665 void errorsClear(){ 666 _errors.length = 0; 667 } 668 /// get a JSON representing the generated AST 669 /// 670 /// Returns: pretty printed JSON 671 string prettyAST(){ 672 return toJSON(_ast).toPrettyString; 673 } 674 /// the generated bytecode. This class will *NOT* be freed by QSCompiler. 675 /// 676 /// Returns: the generated bytecode as NaBytecode 677 QScriptBytecode bytecode(){ 678 return _bytecode; 679 } 680 /// load a script which is to be compiled. 681 /// 682 /// Each element in the array should be a line, without the newline character(s) at end 683 void loadScript(string[] script){ 684 _script = script.dup; 685 } 686 /// generates tokens for a script 687 /// 688 /// Returns: true if done without errors, false if there were errors 689 bool generateTokens(){ 690 _tokens = toTokens(_script, _errors); 691 return _errors.length == 0; 692 } 693 /// generates AST from tokens 694 /// 695 /// Returns: true if done without errors, false if there were errors 696 bool generateAST(){ 697 _ast = _astgenerator.generateScriptAST(_tokens); 698 CompileError[] astErrors = _astgenerator.errors; 699 if (astErrors.length > 0){ 700 _errors ~= astErrors; 701 return false; 702 } 703 return true; 704 } 705 /// checks and finalises generated AST 706 /// 707 /// Returns: true if done without errors, false if there were errors 708 bool finaliseAST(){ 709 if (_scriptExports is null){ 710 _errors ~= CompileError(0, "assign a value to QSCompiler.scriptExports before calling finaliseAST"); 711 return false; 712 } 713 if (_scriptDeclarations !is null) 714 .destroy(_scriptDeclarations); 715 _scriptDeclarations = new Library("_QSCRIPT_TEMP_LIB"); 716 CompileError[] checkErrors = _astcheck.checkAST(_ast, _scriptExports, _scriptDeclarations); 717 if (checkErrors.length){ 718 _errors ~= checkErrors; 719 return false; 720 } 721 return true; 722 } 723 /// ditto 724 alias checkAST = finaliseAST; 725 /// generates bytecode from AST. 726 /// 727 /// Returns: true if done without errors, false if there was some error. The error itself cannot be known if happens in CodeGen, its likely to be a bug in CodeGen. 728 bool generateCode(){ 729 if (_scriptDeclarations is null){ 730 _errors ~= CompileError(0,"QSCompiler.finaliseAST not called before calling QSCompiler.generateCode"); 731 return false; 732 } 733 immutable bool r = _codegen.generateCode(_ast, _scriptDeclarations); 734 .destroy(_scriptDeclarations); 735 _scriptDeclarations = null; 736 _bytecode = _codegen.bytecode; 737 string[] resolveErrors = _bytecode.resolve; 738 if (resolveErrors.length){ 739 foreach (err; resolveErrors) 740 _errors ~= CompileError(0, "[bytecode.resolve]: "~err); 741 return false; 742 } 743 return r; 744 } 745 }