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 }