1 /++ 2 QScript core libraries + some libraries providing some extra functions 3 +/ 4 module qscript.stdlib; 5 6 import qscript.qscript; 7 import qscript.compiler.compiler; 8 9 import utils.misc; 10 11 import std.conv : to; 12 13 /// library providing operators 14 public class OpLibrary : Library{ 15 private: 16 const NUM_OPERATORS = ["opDivide", "opMultiply", "opAdd", "opSubtract", "opMod", "opLesser", "opGreater", 17 "opLesserSame", "opGreaterSame"]; 18 public: 19 /// constructor 20 this(){ 21 super("qscript_operators",true); 22 } 23 /// Adds a new function 24 /// 25 /// Returns: function ID, or -1 if function already exists 26 override integer addFunction(Function func){ 27 DataType dummy; 28 if (super.hasFunction(func.name, func.argTypes, dummy)!=-1) 29 return -1; 30 _functions ~= func; 31 return cast(integer)_functions.length-1; 32 } 33 /// Returns: function ID, or -1 if doesnt exist 34 override integer hasFunction(string name, DataType[] argsType, ref DataType returnType){ 35 // check if it previously assigned an ID to a function of this type 36 integer r = super.hasFunction(name, argsType, returnType); 37 if (r > -1) 38 return r; 39 if (argsType.length == 0) // I got nothing with zero arguments 40 return -1; 41 if (NUM_OPERATORS.hasElement(name)){ 42 returnType = argsType[0]; 43 if (argsType.length == 2 && argsType[0] == argsType[1] && (argsType[0] == DataType(DataType.Type.Int) || 44 argsType[0] == DataType(DataType.Type.Double))) 45 // make a new ID for this 46 return this.addFunction(Function(name, argsType[0], argsType)); 47 return -1; 48 } 49 if (name == "opSame"){ 50 returnType = DataType(DataType.Type.Bool); 51 // must have 2 operands, both of same type, not arrays, and not custom(struct) 52 if (argsType.length == 2 && argsType[0] == argsType[1] && !argsType[0].isArray && 53 !argsType[0].type == DataType.Type.Custom) 54 return this.addFunction(Function(name, DataType(DataType.Type.Bool), argsType)); 55 return -1; 56 } 57 if (name == "opConcat"){ 58 returnType = argsType[0]; 59 // both must be arrays of same dimension with same base data type 60 if (argsType.length == 2 && argsType[0] == argsType[1] && !argsType[0].isRef && argsType[0].arrayDimensionCount > 0) 61 return this.addFunction(Function(name, argsType[0], argsType)); 62 return -1; 63 } 64 if (name == "opAndBool" || name == "opOrBool"){ 65 returnType = DataType(DataType.Type.Bool); 66 if (argsType.length == 2 && argsType[0] == argsType[1] && argsType[0] == DataType(DataType.Type.Bool)) 67 return this.addFunction(Function(name, DataType(DataType.Type.Bool), argsType)); 68 return -1; 69 } 70 if (name == "opNot"){ 71 returnType = DataType(DataType.Type.Bool); 72 if (argsType == [DataType(DataType.Type.Bool)]) 73 return this.addFunction(Function(name, DataType(DataType.Type.Bool), argsType)); 74 return -1; 75 } 76 return -1; 77 } 78 /// Generates bytecode for a function call, or return false 79 /// 80 /// Returns: true if it added code for the function call, false if codegen.d should 81 override bool generateFunctionCallCode(QScriptBytecode bytecode, uinteger functionId, CodeGenFlags flags){ 82 Function func; 83 if (functionId >= this.functions.length) 84 return false; 85 if (!(flags & CodeGenFlags.PushFunctionReturn)) 86 return false; 87 func = this.functions[functionId]; 88 string typePostFix = func.argTypes[0].type == DataType.Type.Int ? "Int" : "Double"; 89 switch (func.name){ 90 case "opDivide": 91 bytecode.addInstruction("mathDivide"~typePostFix, ""); 92 break; 93 case "opMultiply": 94 bytecode.addInstruction("mathMultiply"~typePostFix, ""); 95 break; 96 case "opAdd": 97 bytecode.addInstruction("mathAdd"~typePostFix, ""); 98 break; 99 case "opSubtract": 100 bytecode.addInstruction("mathSubtract"~typePostFix, ""); 101 break; 102 case "opMod": 103 bytecode.addInstruction("mathMod"~typePostFix, ""); 104 break; 105 case "opLesser": // have to use the opposite instruction because of the order these instructions pop A and B in 106 bytecode.addInstruction("isGreater"~typePostFix, ""); 107 break; 108 case "opGreater": 109 bytecode.addInstruction("isLesser"~typePostFix, ""); 110 break; 111 case "opLesserSame": 112 bytecode.addInstruction("isGreaterSame"~typePostFix, ""); 113 break; 114 case "opGreaterSame": 115 bytecode.addInstruction("isLesserSame"~typePostFix, ""); 116 break; 117 case "opSame": 118 bytecode.addInstruction("isSame", ""); 119 break; 120 case "opConcat": 121 bytecode.addInstruction("arrayConcat", ""); 122 break; 123 case "opAndBool": 124 bytecode.addInstruction("And", ""); 125 break; 126 case "opOrBool": 127 bytecode.addInstruction("Or", ""); 128 break; 129 case "opNot": 130 bytecode.addInstruction("Not", ""); 131 break; 132 default: 133 return false; 134 } 135 if (flags & CodeGenFlags.PushRef) 136 bytecode.addInstruction("pushRefFromPop", ""); 137 return true; 138 } 139 } 140 141 /// Library for functions related to arrays 142 public class ArrayLibrary : Library{ 143 public: 144 /// constructor 145 this (){ 146 super("qscript_arrays", true); 147 } 148 /// Adds a new function 149 /// 150 /// Returns: function ID, or -1 if function already exists 151 override integer addFunction(Function func){ 152 DataType dummy; 153 if (super.hasFunction(func.name, func.argTypes, dummy)!=-1) 154 return -1; 155 _functions ~= func; 156 return cast(integer)_functions.length-1; 157 } 158 /// Returns: function ID, or -1 if doesnt exist 159 override integer hasFunction(string name, DataType[] argsType, ref DataType returnType){ 160 integer r = super.hasFunction(name, argsType, returnType); 161 if (r > -1) 162 return r; 163 if (argsType.length == 0) 164 return -1; 165 if (name == "copy"){ 166 returnType = argsType[0]; 167 if (argsType.length == 1 && argsType[0].isArray) 168 return this.addFunction(Function("copy",returnType, argsType)); 169 return -1; 170 } 171 if (name == "length"){ 172 if (!argsType[0].isArray) 173 return -1; 174 if (argsType.length == 1){ 175 returnType = DataType(DataType.Type.Int); 176 return this.addFunction(Function("length",returnType,argsType)); 177 } 178 return -1; 179 } 180 if (name == "setLength"){ 181 if (!argsType[0].isArray) 182 return -1; 183 if (argsType.length == 2 && argsType[1] == DataType(DataType.Type.Int)){ 184 returnType = DataType(DataType.Type.Void); 185 return this.addFunction(Function("length",returnType,argsType)); 186 } 187 return -1; 188 } 189 return -1; 190 } 191 /// setLength uses arrayLengthSet which wants arguments in opposite order 192 override uinteger[] functionCallArgumentsPushOrder(uinteger functionId){ 193 if (functionId >= this.functions.length) 194 return []; 195 if (this.functions[functionId].name == "setLength") 196 return [1,0]; 197 return []; 198 } 199 /// Generates bytecode for a function call, or return false 200 /// 201 /// Returns: true if it added code for the function call, false if codegen.d should 202 override bool generateFunctionCallCode(QScriptBytecode bytecode, uinteger functionId, CodeGenFlags flags){ 203 Function func; 204 if (functionId >= this.functions.length) 205 return false; 206 func = this.functions[functionId]; 207 if (func.name == "copy"){ 208 bytecode.addInstruction("arrayCopy"); 209 }else if (func.name == "length"){ 210 bytecode.addInstruction("arrayLength"); 211 }else if (func.name == "setLength"){ 212 bytecode.addInstruction("arrayLengthSet"); 213 return true; // so it doesnt try and pop the return, coz there is no return 214 } 215 if (!(flags & CodeGenFlags.PushFunctionReturn)) 216 bytecode.addInstruction("pop", ""); 217 else if (flags & CodeGenFlags.PushRef) 218 bytecode.addInstruction("pushRefFromPop", ""); 219 return false; 220 } 221 } 222 223 /// Library for data type conversion 224 public class TypeConvLibrary : Library{ 225 public: 226 /// constructor 227 this(){ 228 super("qscript_typeconv", true); 229 } 230 /// Adds a new function 231 /// 232 /// Returns: function ID, or -1 if function already exists 233 override integer addFunction(Function func){ 234 DataType dummy; 235 if (super.hasFunction(func.name, func.argTypes, dummy)!=-1) 236 return -1; 237 _functions ~= func; 238 return cast(integer)_functions.length-1; 239 } 240 /// Returns: function ID, or -1 if doesnt exist 241 override integer hasFunction(string name, DataType[] argsType, ref DataType returnType){ 242 integer r = super.hasFunction(name, argsType, returnType); 243 if (r > -1) 244 return r; 245 if (argsType.length != 1) 246 return -1; 247 if (name == "toInt"){ 248 returnType = DataType(DataType.Type.Int); 249 // can do char to character code in int, bool to int (0 or 1), or int to double, or int to string 250 if ([DataType(DataType.Type.Char), DataType(DataType.Type.Bool), DataType(DataType.Type.Double), 251 DataType(DataType.Type.Char,1)].hasElement(argsType[0])) 252 return this.addFunction(Function(name, returnType, argsType)); 253 return -1; 254 } 255 if (name == "toChar"){ 256 // for converting character code in int to char 257 returnType = DataType(DataType.Type.Char); 258 if (argsType[0] == DataType(DataType.Type.Int)) 259 return this.addFunction(Function(name, returnType, argsType)); 260 return -1; 261 } 262 if (name == "toDouble"){ 263 // can convert int to double, and string to double, nothing else 264 returnType = DataType(DataType.Type.Double); 265 if ([DataType(DataType.Type.Int), DataType(DataType.Type.Char,1)].hasElement(argsType[0])) 266 return this.addFunction(Function(name, returnType, argsType)); 267 return -1; 268 } 269 if (name == "toString"){ 270 // can convert int, double, and bool to string 271 returnType = DataType(DataType.Type.Char,1); 272 if ([DataType(DataType.Type.Int),DataType(DataType.Type.Double),DataType(DataType.Type.Bool)].hasElement(argsType)) 273 return this.addFunction(Function(name, returnType, argsType)); 274 return -1; 275 } 276 return -1; 277 } 278 /// Generates bytecode for a function call, or return false 279 /// 280 /// Returns: true if it added code for the function call, false if codegen.d should 281 override bool generateFunctionCallCode(QScriptBytecode bytecode, uinteger functionId, CodeGenFlags flags){ 282 Function func; 283 if (functionId >= this.functions.length) 284 return false; 285 if (!(flags & CodeGenFlags.PushFunctionReturn)) 286 return false; 287 func = this.functions[functionId]; 288 if (func.name == "toInt"){ 289 if (func.argTypes[0] == DataType(DataType.Type.Double)) 290 bytecode.addInstruction("doubleToInt", ""); 291 else if (func.argTypes[0] == DataType(DataType.Type.Char, 1)) 292 bytecode.addInstruction("stringToInt", ""); 293 else if (func.argTypes[0] != DataType(DataType.Type.Char) && func.argTypes[0] != DataType(DataType.Type.Bool)) 294 return false; // those data types (in condition above) are valid, but no byte code is generated for those. 295 }else if (func.name == "toChar"){ 296 if (func.argTypes[0] != DataType(DataType.Type.Int)) 297 return false; // toChar only works with int 298 // no need to generate any code 299 }else if (func.name == "toDouble"){ 300 if (func.argTypes[0] == DataType(DataType.Type.Int)) 301 bytecode.addInstruction("intToDouble", ""); 302 else if (func.argTypes[0] == DataType(DataType.Type.Char,1)) 303 bytecode.addInstruction("stringToDouble", ""); 304 else 305 return false; 306 }else if (func.name == "toString"){ 307 if (func.argTypes[0] == DataType(DataType.Type.Int)) 308 bytecode.addInstruction("intToString", ""); 309 else if (func.argTypes[0] == DataType(DataType.Type.Double)) 310 bytecode.addInstruction("doubleToString", ""); 311 else if (func.argTypes[0] == DataType(DataType.Type.Bool)) 312 bytecode.addInstruction("boolToString", ""); 313 else 314 return false; 315 }else 316 return false; 317 if (flags & CodeGenFlags.PushRef) 318 bytecode.addInstruction("pushRefFromPop", ""); 319 return true; 320 } 321 } 322 323 /// Library for std input/output 324 public class StdIOLibrary : Library{ 325 private: 326 import std.stdio : write, writeln, readln; 327 import std..string : chomp; 328 NaData writelnStr(NaData[] args){ 329 foreach (arg; args) 330 writeln(arg.strVal); 331 return NaData(0); 332 } 333 NaData writeStr(NaData[] args){ 334 foreach (arg; args) 335 write(arg.strVal); 336 return NaData(0); 337 } 338 NaData readStr(){ 339 return NaData(readln.chomp().to!dstring); 340 } 341 public: 342 /// constructor 343 this(){ 344 super("qscript_stdio", false); 345 } 346 /// Returns: function ID, or -1 if doesnt exist 347 override integer hasFunction(string name, DataType[] argsType, ref DataType returnType){ 348 integer r = super.hasFunction(name, argsType, returnType); 349 if (r > -1) 350 return r; 351 returnType = DataType(DataType.Type.Void); 352 if (name == "writeln" && argsType.length > 0){ 353 foreach (argType; argsType){ 354 if (argType != DataType(DataType.Type.Char, 1)) 355 return -1; 356 } 357 /// just return 0 on it, its not like its gonna ask to send over function with id=0 (at least not yet) 358 return 0; 359 } 360 if (name == "write" && argsType.length > 0){ 361 foreach (argType; argsType){ 362 if (argType != DataType(DataType.Type.Char, 1)) 363 return -1; 364 } 365 return 1; 366 } 367 if (name == "read" && argsType.length == 0){ 368 returnType = DataType(DataType.Type.Char,1); 369 return 2; 370 } 371 return -1; 372 } 373 /// Executes a library function 374 /// 375 /// Returns: whatever that function returned 376 override NaData execute(uinteger functionId, NaData[] args){ 377 switch (functionId){ 378 case 0: 379 return writelnStr(args); 380 case 1: 381 return writeStr(args); 382 case 2: 383 return readStr(); 384 default: 385 return NaData(0); 386 } 387 } 388 }