1 /++ 2 Contains everything needed to run QScript scripts. 3 +/ 4 module qscript.qscript; 5 6 import utils.misc; 7 import utils.lists; 8 9 import qscript.compiler.compiler; 10 import qscript.stdlib; 11 12 import std.conv:to; 13 14 import navm.navm; 15 import navm.bytecode; 16 import navm.defs : ArrayStack; 17 18 alias ExternFunction = NaData delegate(NaData[]); 19 alias NaData = navm.navm.NaData; 20 alias CompileError = qscript.compiler.compiler.CompileError; 21 22 /// To store a library (could be a script as a library as well) 23 public class Library{ 24 protected: 25 /// if this library is automatically imported 26 bool _autoImport; 27 /// name of library 28 string _name; 29 /// functions exported by library. The index is function ID. 30 Function[] _functions; 31 /// global variables exported by this library. index is ID 32 Variable[] _vars; 33 /// structs exported by library 34 Struct[] _structs; 35 /// Enums exported by library 36 Enum[] _enums; 37 public: 38 /// constructor 39 this(string name, bool autoImport = false){ 40 _name = name; 41 this._autoImport = autoImport; 42 } 43 /// if this library is automatically imported 44 @property bool autoImport(){ 45 return _autoImport; 46 } 47 /// library name 48 @property string name(){ 49 return _name; 50 } 51 /// functions exported by library. The index is function ID. 52 @property Function[] functions(){ 53 return _functions; 54 } 55 /// Adds a new function 56 /// 57 /// Returns: function ID, or -1 if function already exists 58 integer addFunction(Function func){ 59 if (this.hasFunction(func.name, func.argTypes)!=-1) 60 return -1; 61 _functions ~= func; 62 return cast(integer)_functions.length-1; 63 } 64 /// global variables exported by this library. index is ID 65 @property ref Variable[] vars(){ 66 return _vars; 67 } 68 /// Adds a new variable. 69 /// 70 /// Returns: Variable ID, or -1 if it already exists 71 integer addVar(Variable var){ 72 if (this.hasVar(var.name)) 73 return -1; 74 _vars ~= var; 75 return cast(integer)_vars.length-1; 76 } 77 /// structs exported by library 78 @property ref Struct[] structs(){ 79 return _structs; 80 } 81 /// Adds a new struct 82 /// 83 /// Returns: struct ID, or -1 if already exists 84 integer addStruct(Struct str){ 85 if (this.hasStruct(str.name)) 86 return -1; 87 _structs ~= str; 88 return cast(integer)_structs.length-1; 89 } 90 /// Enums exported by library 91 @property ref Enum[] enums() { 92 return _enums; 93 } 94 /// Adds a new enum 95 /// 96 /// Returns: enum ID, or -1 if it already exists 97 integer addEnum(Enum enu){ 98 if (this.hasEnum(enu.name)) 99 return -1; 100 _enums ~= enu; 101 return cast(integer)_enums.length-1; 102 } 103 /// Returns: true if struct exists 104 bool hasStruct(string name, ref Struct str){ 105 foreach (currentStruct; _structs){ 106 if (currentStruct.name == name){ 107 str = currentStruct; 108 return true; 109 } 110 } 111 return false; 112 } 113 /// ditto 114 bool hasStruct(string name){ 115 foreach (currentStruct; _structs){ 116 if (currentStruct.name == name) 117 return true; 118 } 119 return false; 120 } 121 /// Returns: true if enum exists 122 bool hasEnum(string name, ref Enum enu){ 123 foreach (currentEnum; _enums){ 124 if (currentEnum.name == name){ 125 enu = currentEnum; 126 return true; 127 } 128 } 129 return false; 130 } 131 /// ditto 132 bool hasEnum(string name){ 133 foreach (currentEnum; _enums){ 134 if (currentEnum.name == name) 135 return true; 136 } 137 return false; 138 } 139 /// Returns: variable ID, or -1 if doesnt exist 140 integer hasVar(string name, ref DataType type){ 141 foreach (i, var; _vars){ 142 if (var.name == name){ 143 type = var.type; 144 return i; 145 } 146 } 147 return -1; 148 } 149 /// Returns: true if variable exists 150 bool hasVar(string name){ 151 foreach (var; _vars){ 152 if (var.name == name) 153 return true; 154 } 155 return false; 156 } 157 /// Returns: function ID, or -1 if doesnt exist 158 integer hasFunction(string name, DataType[] argsType, ref DataType returnType){ 159 foreach (i, func; _functions){ 160 if (func.name == name){ 161 if (argsType.length == func.argTypes.length){ 162 foreach (j; 0 .. argsType.length){ 163 if (!argsType[j].canImplicitCast(func.argTypes[j])) 164 return -1; 165 } 166 returnType = func.returnType; 167 return i; 168 } 169 } 170 } 171 return -1; 172 } 173 /// ditto 174 integer hasFunction(string name, DataType[] argsType){ 175 DataType returnType; 176 return this.hasFunction(name, argsType, returnType); 177 } 178 /// Returns: true if a function by a name exists 179 bool hasFunction(string name){ 180 foreach (func; _functions){ 181 if (func.name == name) 182 return true; 183 } 184 return false; 185 } 186 /// For use with generateFunctionCallCode. Use this to change what order arguments are pushed to stack 187 /// before the bytecode for functionCall is generated 188 /// 189 /// Returns: the order of arguments to push (`[1,0]` will push 2 arguments in reverse), or [] for default 190 uinteger[] functionCallArgumentsPushOrder(uinteger functionId){ 191 return []; 192 } 193 /// Generates bytecode for a function call, or return false 194 /// 195 /// This function must consider all flags 196 /// 197 /// Returns: true if it added code for the function call, false if codegen.d should 198 bool generateFunctionCallCode(QScriptBytecode bytecode, uinteger functionId, CodeGenFlags flags){ 199 return false; 200 } 201 /// Generates bytecode that will push value of a variable to stack, or return false 202 /// 203 /// This function must consider all flags 204 /// 205 /// Returns: true if it added code, false if codegen.d should 206 bool generateVariableCode(QScriptBytecode bytecode, uinteger variableId, CodeGenFlags flags){ 207 return false; 208 } 209 /// Executes a library function 210 /// 211 /// Returns: whatever that function returned 212 NaData execute(uinteger functionId, NaData[] args){ 213 return NaData(0); 214 } 215 /// Returns: value of a variable 216 NaData getVar(uinteger varId){ 217 return NaData(0); 218 } 219 /// Sets value of a variable 220 NaData getVarRef(uinteger varId){ 221 return NaData(null); 222 } 223 /// Writes this library to a single printable string 224 /// 225 /// Returns: string representing contents of this library 226 override string toString(){ 227 char[] r = cast(char[])"library|"~name~'|'~_autoImport.to!string~'|'; 228 foreach (func; _functions) 229 r ~= func.toString ~ '/'; 230 r ~= '|'; 231 foreach (str; _structs) 232 r ~= str.toString ~ '/'; 233 r ~= '|'; 234 foreach (enu; _enums) 235 r ~= enu.toString ~ '/'; 236 r ~= '|'; 237 foreach (var; _vars) 238 r ~= var.toString ~ '/'; 239 r ~= '|'; 240 return cast(string)r; 241 } 242 /// Reads this library from a string (reverse of toString) 243 /// 244 /// Returns: empty string in case of success, error in string in case of error 245 string fromString(string libraryString){ 246 string[] vals = commaSeparate!('|',false)(libraryString); 247 if (vals.length < 7 || vals[0] != "library") 248 return "invalid string to read Library from"; 249 _name = vals[1]; 250 _autoImport = vals[2] == "true" ? true : false; 251 vals = vals[3 .. $]; 252 string[] subVals = vals[0].commaSeparate!'/'; 253 string error; 254 _functions = []; 255 _structs = []; 256 _enums = []; 257 _vars = []; 258 _functions.length = subVals.length; 259 foreach (i, val; subVals){ 260 error = _functions[i].fromString(val); 261 if (error.length) 262 return error; 263 } 264 subVals = vals[1].commaSeparate!'/'; 265 _structs.length = subVals.length; 266 foreach (i, val; subVals){ 267 error = _structs[i].fromString(val); 268 if (error.length) 269 return error; 270 } 271 subVals = vals[2].commaSeparate!'/'; 272 _enums.length = subVals.length; 273 foreach (i, val; subVals){ 274 error = _enums[i].fromString(val); 275 if (error.length) 276 return error; 277 } 278 subVals = vals[3].commaSeparate!'/'; 279 _vars.length = subVals.length; 280 foreach (i, val; subVals){ 281 error = _vars[i].fromString(val); 282 if (error.length) 283 return error; 284 } 285 return []; 286 } 287 } 288 /// 289 unittest{ 290 Library dummyLib = new Library("dummyLib",true); 291 dummyLib.addEnum(Enum("Potatoes",["red","brown"])); 292 dummyLib.addEnum(Enum("Colors", ["red", "brown"])); 293 dummyLib.addFunction(Function("main",DataType("void"),[DataType("char[][]")])); 294 dummyLib.addFunction(Function("add",DataType("int"),[DataType("int"),DataType("int")])); 295 dummyLib.addVar(Variable("abc",DataType("char[]"))); 296 Library loadedLib = new Library("blabla",false); 297 loadedLib.fromString(dummyLib.toString); 298 assert(loadedLib.name == dummyLib.name); 299 assert(loadedLib._autoImport == dummyLib._autoImport); 300 assert(loadedLib._functions == dummyLib._functions); 301 assert(loadedLib._structs == dummyLib._structs); 302 assert(loadedLib._enums == dummyLib._enums); 303 assert(loadedLib._vars == dummyLib._vars); 304 } 305 306 private class QScriptVM : NaVM{ 307 private: 308 Library[] _libraries; 309 NaData _retVal; 310 protected: 311 void call(){ 312 immutable uinteger libId = _stack.pop.intVal, funcID = _stack.pop.intVal; 313 _retVal = _libraries[libId].execute(funcID, _stack.pop(_arg.intVal-2)); 314 } 315 void retValSet(){ 316 _retVal = _stack.pop; 317 } 318 void retValPush(){ 319 _stack.push(_retVal); 320 _retVal.intVal = 0; 321 } 322 323 void makeArrayN(){ 324 NaData array; 325 array.makeArray(_arg.intVal); 326 _stack.push(array); 327 } 328 void arrayCopy(){ 329 _stack.push(NaData(_stack.pop.arrayVal)); 330 } 331 void arrayElement(){ 332 _stack.push(*(_stack.pop.ptrVal + _arg.intVal)); 333 } 334 void arrayElementWrite(){ 335 // left side should evaluate first (if my old comments are right) 336 *(_stack.pop.ptrVal + _arg.intVal) = _stack.pop; 337 } 338 void arrayConcat(){ 339 NaData array, a, b; 340 b = _stack.pop; 341 a = _stack.pop; 342 array.makeArray(a.arrayValLength + b.arrayValLength); 343 array.arrayVal[0 .. a.arrayValLength] = a.arrayVal; 344 array.arrayVal[a.arrayValLength+1 .. $] = b.arrayVal; 345 _stack.push(array); 346 } 347 void incRefN(){ 348 _stack.push(NaData(cast(NaData*)(_stack.pop.ptrVal + _arg.intVal))); 349 } 350 void pushRefFromPop(){ 351 _stack.push(NaData(&(_stack.pop))); 352 } 353 354 void jumpFrameN(){ 355 import navm.defs : StackFrame; 356 immutable uinteger offset = _stack.pop.intVal; 357 _jumpStack.push(StackFrame(_inst, _arg, _stackIndex)); 358 _inst = &(_instructions)[_arg.intVal] - 1; 359 _arg = &(_arguments)[_arg.intVal] - 1; 360 _stackIndex = _stack.count - offset; 361 _stack.setIndex(_stackIndex); 362 } 363 public: 364 /// constructor 365 this(uinteger stackLength = 65_536){ 366 super(stackLength); 367 addInstruction(NaInstruction("call",0x40,true,255,1,&call)); 368 addInstruction(NaInstruction("retValSet",0x41,1,0,&retValSet)); 369 addInstruction(NaInstruction("retValPush",0x42,0,1,&retValPush)); 370 addInstruction(NaInstruction("makeArrayN",0x43,true,0,1,&makeArrayN)); 371 addInstruction(NaInstruction("arrayCopy",0x44,1,1,&arrayCopy)); 372 addInstruction(NaInstruction("arrayElement",0x45,true,1,1,&arrayElement)); 373 addInstruction(NaInstruction("arrayElementWrite",0x46,true,2,0,&arrayElementWrite)); 374 addInstruction(NaInstruction("arrayConcat",0x47,2,1,&arrayConcat)); 375 addInstruction(NaInstruction("incRefN",0x48,true,1,1,&incRefN)); 376 addInstruction(NaInstruction("pushRefFromPop",0x49,1,1,&pushRefFromPop)); 377 addInstruction(NaInstruction("jumpFrameN",0x4A,true,true,1,0,&jumpFrameN)); 378 } 379 /// gets element at an index on stack 380 /// 381 /// Returns: the element 382 NaData getElement(uinteger index){ 383 return _stack.readAbs(index); 384 } 385 /// gets pointer to element at an index on stack 386 /// 387 /// Returns: the pointer to element 388 NaData* getElementPtr(uinteger index){ 389 return _stack.readPtrAbs(index); 390 } 391 /// pushes some data, set _stackIndex=0, and start execution at an instruction 392 /// 393 /// Returns: return value 394 NaData executeFunction(uinteger index, NaData[] toPush){ 395 _stack.push(toPush); 396 execute(index); 397 NaData r = _retVal; 398 _retVal = NaData(0); 399 return r; 400 } 401 } 402 403 /// Stores QScript bytecode 404 class QScriptBytecode : NaBytecode{ 405 private: 406 string _linkInfo; 407 408 public: 409 /// constructor 410 this(NaInstruction[] instructionTable){ 411 super(instructionTable); 412 } 413 /// link info 414 @property string linkInfo(){ 415 return _linkInfo; 416 } 417 /// ditto 418 @property string linkInfo(string newVal){ 419 return _linkInfo = newVal; 420 } 421 /// Returns: the bytecode in a readable format 422 override string[] getBytecodePretty(){ 423 return [ 424 "#","#"~linkInfo 425 ]~super.getBytecodePretty(); 426 } 427 /// Reads from a string[] (follows spec/syntax.md) 428 /// 429 /// Returns: errors in a string[], or [] if no errors 430 override string[] readByteCode(string[] input){ 431 if (input.length < 2 || input[1].length < 2 || input[1][0] != '#') 432 return ["not a valid QScript Bytecode"]; 433 _linkInfo = input[1][1 .. $]; 434 return super.readByteCode(input[2 .. $]); 435 } 436 } 437 438 /// to execute a script, or use a script as a library 439 public class QScript : Library{ 440 private: 441 QScriptVM _vm; 442 QSCompiler _compiler; 443 /// number of default libraries 444 uinteger _defLibCount; 445 public: 446 /// constructor. 447 /// 448 /// Set stack length of VM here, default should be more than enough 449 this(string scriptName, bool autoImport, Library[] libraries, bool defaultLibs = true, bool extraLibs = true){ 450 super(scriptName, autoImport); 451 _vm = new QScriptVM(); 452 _defLibCount = 0; 453 if (defaultLibs){ 454 _vm._libraries = [ 455 new OpLibrary(), 456 new ArrayLibrary(), 457 new TypeConvLibrary(), 458 ]; 459 _defLibCount = _vm._libraries.length; 460 } 461 if (extraLibs){ 462 _vm._libraries ~= new StdIOLibrary(); 463 _defLibCount += 1; 464 } 465 _vm._libraries ~= libraries.dup; 466 _compiler = new QSCompiler(_vm._libraries, _vm.instructionTable); 467 } 468 ~this(){ 469 .destroy(_compiler); 470 foreach (i; 0 .. _defLibCount) 471 .destroy(_vm._libraries[i]); 472 .destroy(_vm); 473 } 474 // overriding public functions that wont be needed 475 override integer addFunction(Function){ 476 return -1; 477 } 478 override integer addStruct(Struct){ 479 return -1; 480 } 481 override integer addEnum(Enum){ 482 return -1; 483 } 484 override integer addVar(Variable){ 485 return -1; 486 } 487 /// Executes a function from script 488 /// 489 /// Returns: whatever that function returned, or random data 490 override NaData execute(uinteger functionId, NaData[] args){ 491 return _vm.executeFunction(functionId, args); 492 } 493 override NaData getVar(uinteger varId){ 494 return _vm.getElement(varId); 495 } 496 override NaData getVarRef(uinteger varId){ 497 return NaData(_vm.getElementPtr(varId)); 498 } 499 /// compiles a script, and prepares it for execution with this class 500 /// 501 /// Returns: bytecode, or null in case of error 502 /// the returned bytecode will not be freed by this class, so you should do it when not needed 503 QScriptBytecode compileScript(string[] script, ref CompileError[] errors){ 504 // clear itself 505 _functions.length = 0; 506 _vars.length = 0; 507 _structs.length = 0; 508 _enums.length = 0; 509 // prepare compiler 510 _compiler.scriptExports = this; 511 _compiler.errorsClear; 512 _compiler.loadScript(script); 513 // start compiling 514 if (!_compiler.generateTokens || !_compiler.generateAST || !_compiler.finaliseAST || !_compiler.generateCode){ 515 errors = _compiler.errors; 516 return null; 517 } 518 QScriptBytecode bytecode = _compiler.bytecode; 519 string[] sErrors; 520 if (!this.load(bytecode, sErrors)) 521 foreach (err; sErrors) 522 errors ~= CompileError(0, err); 523 return bytecode; 524 } 525 /// compiles a script, and prepares it for execution with this class 526 /// 527 /// Returns: errors, if any, or empty array 528 CompileError[] compileScript(string[] script){ 529 CompileError[] r; 530 this.compileScript(script, r).destroy(); 531 return r; 532 } 533 534 /// Loads bytecode and library info 535 /// 536 /// the bytecode will not be freed by this class, so you should do it when not needed 537 bool load(QScriptBytecode bytecode, string[] errors){ 538 errors = bytecode.resolve; 539 if (errors.length) 540 return false; 541 errors = [this.fromString(bytecode.linkInfo)]; 542 if (errors[0] != "") 543 return false; 544 errors = []; 545 errors = _vm.load(bytecode); 546 if (errors.length) 547 return false; 548 return true; 549 } 550 }