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 }