1 /++
2 For generating byte code from AST
3 +/
4 module qscript.compiler.codegen;
5 
6 import qscript.compiler.ast;
7 import qscript.compiler.astcheck;
8 import qscript.compiler.misc;
9 
10 import utils.misc;
11 import utils.lists;
12 
13 import navm.navm : Instruction, NaData;
14 import navm.navm : readData;
15 import navm.bytecodedefs; // needed to check how many elements instruction wants from stack
16 
17 import std.conv : to;
18 
19 /// Contains functions to generate ByteCode from AST nodes
20 class CodeGen{
21 private:
22 	/// writer
23 	NaByteCodeWriter _writer;
24 	/// stores script defined functions
25 	Function[] _functions;
26 protected:
27 	/// generates byte code for a FunctionNode
28 	void generateByteCode(FunctionNode node){
29 		_writer.startFunction(node.arguments.length);
30 		// use push to reserve space on stack for variables
31 		foreach (i; 0 .. node.varCount - node.arguments.length){
32 			_writer.addInstruction(Instruction.Push, ["0"]);
33 		}
34 		generateByteCode(node.bodyBlock);
35 		// don't forget to Terminate
36 		_writer.addInstruction(Instruction.Terminate);
37 		_writer.appendFunction();
38 		// add itself to _functions
39 		Function itself;
40 		itself.name = node.name;
41 		itself.returnType = node.returnType;
42 		itself.argTypes.length = node.arguments.length;
43 		foreach (i, arg; node.arguments)
44 			itself.argTypes[i] = arg.argType;
45 		_functions[node.id] = itself;
46 	}
47 	/// generates byte code for a BlockNode
48 	void generateByteCode(BlockNode node){
49 		foreach (statement; node.statements){
50 			generateByteCode(statement);
51 		}
52 	}
53 	/// generates byte code for a StatementNode
54 	void generateByteCode(StatementNode node){
55 		if (node.type == StatementNode.Type.Assignment){
56 			generateByteCode(node.node!(StatementNode.Type.Assignment));
57 		}else if (node.type == StatementNode.Type.Block){
58 			generateByteCode(node.node!(StatementNode.Type.Block));
59 		}else if (node.type == StatementNode.Type.DoWhile){
60 			generateByteCode(node.node!(StatementNode.Type.DoWhile));
61 		}else if (node.type == StatementNode.Type.For){
62 			generateByteCode(node.node!(StatementNode.Type.For));
63 		}else if (node.type == StatementNode.Type.FunctionCall){
64 			generateByteCode(node.node!(StatementNode.Type.FunctionCall));
65 			_writer.addInstruction(Instruction.Pop);
66 		}else if (node.type == StatementNode.Type.If){
67 			generateByteCode(node.node!(StatementNode.Type.If));
68 		}else if (node.type == StatementNode.Type.VarDeclare){
69 			generateByteCode(node.node!(StatementNode.Type.VarDeclare));
70 		}else if (node.type == StatementNode.Type.While){
71 			generateByteCode(node.node!(StatementNode.Type.While));
72 		}else if (node.type == StatementNode.Type.Return){
73 			generateByteCode(node.node!(StatementNode.Type.Return));
74 		}
75 	}
76 	/// generates byte code for AssignmentNode
77 	void generateByteCode(AssignmentNode node){
78 		// first get the value
79 		generateByteCode(node.val);
80 		// if being assigned to ref, just use pushFrom + writeToRef, else, do writeTo
81 		if (node.deref){
82 			_writer.addInstruction(Instruction.PushFrom, [to!string(node.var.id)]); // this gets the ref
83 			_writer.addInstruction(Instruction.WriteToRef); // and this writes value to ref
84 		}else{
85 			// just do pushTo 
86 			_writer.addInstruction(Instruction.WriteTo, [to!string(node.var.id)]);
87 		}
88 	}
89 	/// generates ByteCode for DoWhileNode
90 	void generateByteCode(DoWhileNode node){
91 		// get the index of where the loop starts
92 		immutable uinteger startIndex = _writer.instructionCount;
93 		// now comes loop body
94 		generateByteCode(node.statement);
95 		// condition
96 		generateByteCode(node.condition);
97 		_writer.addInstruction(Instruction.JumpIf, [to!string(startIndex)]);// jump to startIndex if condition == 1
98 	}
99 	/// generates byte code for ForNode
100 	void generateByteCode(ForNode node){
101 		generateByteCode(node.initStatement);
102 		// condition
103 		immutable uinteger conditionIndex = _writer.instructionCount;
104 		generateByteCode(node.condition);
105 		_writer.addInstruction(Instruction.Not);
106 		immutable uinteger jumpInstIndex = _writer.instructionCount; // index of jump instruction
107 		_writer.addInstruction(Instruction.JumpIf, ["0"]); //placeholder, will write jumpToIndex later
108 		// loop body
109 		generateByteCode(node.statement);
110 		// now the increment statement
111 		generateByteCode(node.incStatement);
112 		// jump back
113 		_writer.addInstruction(Instruction.Jump, [to!string(conditionIndex)]);
114 		// now update the jump to jump ahead of this
115 		_writer.changeJumpArg(jumpInstIndex, _writer.instructionCount);
116 	}
117 	/// generates byte code for FunctionCallNode
118 	void generateByteCode(FunctionCallNode node){
119 		if (!node.isScriptDefined && node.isInBuilt){
120 			generateInBuiltFunctionByteCode(node);
121 		}else{
122 			// push args
123 			foreach (arg; node.arguments){
124 				generateByteCode(arg);
125 			}
126 			_writer.addInstruction(node.isScriptDefined ? Instruction.ExecuteFunction : Instruction.ExecuteFunctionExternal,
127 				[to!string(node.id), to!string(node.arguments.length)]);
128 		}
129 	}
130 	/// generates byte code for inbuilt QScript functions (`length(void[])` and stuff)
131 	void generateInBuiltFunctionByteCode(FunctionCallNode node){
132 		/// argument types of function call
133 		DataType[] argTypes;
134 		argTypes.length = node.arguments.length;
135 		foreach (i, arg; node.arguments){
136 			argTypes[i] = arg.returnType;
137 		}
138 		/// encoded name of function
139 		string fName = encodeFunctionName(node.fName, argTypes);
140 		/// length(@void[], int)
141 		if (node.fName == "length"){
142 			if (matchArguments([DataType(DataType.Type.Void, 1, true), DataType(DataType.Type.Integer)],argTypes)){
143 				// set array length
144 				generateByteCode(node.arguments[1]); // length comes first, coz it's popped later
145 				generateByteCode(node.arguments[0]);
146 				_writer.addInstruction(Instruction.ArrayLengthSet);
147 			}else if (matchArguments([DataType(DataType.Type.Void, 1)], argTypes) ||
148 			matchArguments([DataType(DataType.Type.String)], argTypes)){
149 				// get array/string length
150 				generateByteCode(node.arguments[0]);
151 				_writer.addInstruction(Instruction.ArrayLength);
152 			}
153 		}else if (fName == encodeFunctionName("toInt", [DataType(DataType.Type.String)])){
154 			/// toInt(string)
155 			generateByteCode(node.arguments[0]);
156 			_writer.addInstruction(Instruction.StringToInt);
157 		}else if (fName == encodeFunctionName("toInt", [DataType(DataType.Type.Double)])){
158 			/// toInt(double)
159 			generateByteCode(node.arguments[0]);
160 			_writer.addInstruction(Instruction.DoubleToInt);
161 		}else if (fName == encodeFunctionName("toDouble", [DataType(DataType.Type.String)])){
162 			/// toDouble(string)
163 			generateByteCode(node.arguments[0]);
164 			_writer.addInstruction(Instruction.StringToDouble);
165 		}else if (fName == encodeFunctionName("toDouble", [DataType(DataType.Type.Integer)])){
166 			/// toDouble(int)
167 			generateByteCode(node.arguments[0]);
168 			_writer.addInstruction(Instruction.IntToDouble);
169 		}else if (fName == encodeFunctionName("toStr", [DataType(DataType.Type.Integer)])){
170 			/// toStr(int)
171 			generateByteCode(node.arguments[0]);
172 			_writer.addInstruction(Instruction.IntToString);
173 		}else if (fName == encodeFunctionName("toStr", [DataType(DataType.Type.Double)])){
174 			/// toStr(double)
175 			generateByteCode(node.arguments[0]);
176 			_writer.addInstruction(Instruction.DoubleToString);
177 		}
178 	}
179 	/// generates byte code for IfNode
180 	void generateByteCode(IfNode node){
181 		generateByteCode(node.condition);
182 		_writer.addInstruction(Instruction.Not);
183 		immutable uinteger skipToElseInstIndex = _writer.instructionCount; /// index of jumpIf that jumps when false
184 		_writer.addInstruction(Instruction.JumpIf, ["0"]); // placeholder
185 		// now comes the if true part
186 		generateByteCode(node.statement);
187 		// update skipToElse jump
188 		_writer.changeJumpArg(skipToElseInstIndex, _writer.instructionCount);
189 		if (node.hasElse){
190 			// add a jump in the if-true statement to skip this (arg is placeholder)
191 			immutable skipToEndInst = _writer.instructionCount;
192 			_writer.addInstruction(Instruction.Jump);
193 			generateByteCode(node.elseStatement);
194 			// update the skipToEnd jump
195 			_writer.changeJumpArg(skipToEndInst, _writer.instructionCount);
196 		}
197 	}
198 	/// generates byte code for VarDeclareNode - actually, just checks if a value is being assigned to it, if yes, makes var a ref to that val
199 	void generateByteCode(VarDeclareNode node){
200 		foreach (varName; node.vars){
201 			if (node.hasValue(varName))
202 				generateByteCode(node.getValue(varName));
203 			else
204 				_writer.addInstruction(Instruction.Push, ["0"]);
205 			_writer.addInstruction(Instruction.WriteTo, [to!string(node.varIDs[varName])]);
206 		}
207 	}
208 	/// generates byte code for WhileNode
209 	void generateByteCode(WhileNode node){
210 		immutable uinteger conditionIndex = _writer.instructionCount;
211 		generateByteCode(node.condition);
212 		_writer.addInstruction(Instruction.Not);
213 		immutable uinteger jumpInstIndex = _writer.instructionCount;
214 		_writer.addInstruction(Instruction.JumpIf, ["0"]);
215 		generateByteCode(node.statement);
216 		_writer.addInstruction(Instruction.Jump, [conditionIndex.to!string]);
217 		_writer.changeJumpArg(jumpInstIndex, _writer.instructionCount);
218 	}
219 	/// generates byte code for ReturnNode
220 	void generateByteCode(ReturnNode node){
221 		generateByteCode(node.value);
222 		_writer.addInstruction(Instruction.ReturnVal);
223 		// remember, return terminates execution in qscript, not in navm
224 		_writer.addInstruction(Instruction.Terminate);
225 	}
226 
227 	/// generates byte code for CodeNode
228 	void generateByteCode(CodeNode node){
229 		if (node.type == CodeNode.Type.FunctionCall){
230 			generateByteCode(node.node!(CodeNode.Type.FunctionCall));
231 		}else if (node.type == CodeNode.Type.Literal){
232 			generateByteCode(node.node!(CodeNode.Type.Literal));
233 		}else if (node.type == CodeNode.Type.Operator){
234 			generateByteCode(node.node!(CodeNode.Type.Operator));
235 		}else if (node.type == CodeNode.Type.SOperator){
236 			generateByteCode(node.node!(CodeNode.Type.SOperator));
237 		}else if (node.type == CodeNode.Type.ReadElement){
238 			generateByteCode(node.node!(CodeNode.Type.ReadElement));
239 		}else if (node.type == CodeNode.Type.Variable){
240 			generateByteCode(node.node!(CodeNode.Type.Variable));
241 		}else if (node.type == CodeNode.Type.Array){
242 			generateByteCode(node.node!(CodeNode.Type.Array));
243 		}
244 	}
245 	/// generates byte code for LiteralNode
246 	void generateByteCode(LiteralNode node){
247 		_writer.addInstruction(Instruction.Push, [node.literal]);
248 	}
249 	/// generates byte code for OperatorNode
250 	void generateByteCode(OperatorNode node){
251 		bool isFloat = false;
252 		if (node.operands[0].returnType == DataType(DataType.Type.Double) ||
253 			node.operands[1].returnType == DataType(DataType.Type.Double)){
254 			isFloat = true;
255 		}
256 		Instruction opInst;
257 		switch (node.operator){
258 		case "/":
259 			opInst = isFloat ? Instruction.MathDivideDouble : Instruction.MathDivideInt;
260 			break;
261 		case "*":
262 			opInst = isFloat ? Instruction.MathMultiplyDouble : Instruction.MathMultiplyInt;
263 			break;
264 		case "+":
265 			opInst = isFloat ? Instruction.MathAddDouble : Instruction.MathAddInt;
266 			break;
267 		case "-":
268 			opInst = isFloat ? Instruction.MathSubtractDouble : Instruction.MathSubtractInt;
269 			break;
270 		case "%":
271 			opInst = isFloat ? Instruction.MathModDouble : Instruction.MathModInt;
272 			break;
273 		case "<":
274 			// the the end, just flip the oprands and use > (isGreater) because < doesnt exist in navm
275 			opInst = isFloat ? Instruction.IsGreaterDouble : Instruction.IsGreaterInt;
276 			break;
277 		case ">":
278 			opInst = isFloat ? Instruction.IsGreaterDouble : Instruction.IsGreaterInt;
279 			break;
280 		case "<=":
281 			// the the end, just flip the oprands and use >= (isGreaterSame) because <= doesnt exist in navm
282 			opInst = isFloat ? Instruction.IsGreaterSameDouble : Instruction.IsGreaterSameInt;
283 			break;
284 		case ">=":
285 			opInst = isFloat ? Instruction.IsGreaterSameDouble : Instruction.IsGreaterSameInt;
286 			break;
287 		case "==":
288 			opInst = Instruction.IsSame;
289 			break;
290 		case "&&":
291 			opInst = Instruction.And;
292 			break;
293 		case "||":
294 			opInst = Instruction.Or;
295 			break;
296 		default:
297 			break;
298 		}
299 		// if operator is < or <=, then need to flip operands and change operator to > or >=
300 		if (node.operator == "<" || node.operator == "<="){
301 			node.operator = node.operator == "<" ? ">" : ">=";
302 			CodeNode operand = node.operands[0];
303 			node.operands[0] = node.operands[1];
304 			node.operands[1] = operand;
305 		}
306 		generateByteCode(node.operands[1]);
307 		generateByteCode(node.operands[0]);
308 		_writer.addInstruction(opInst);
309 	}
310 	/// generates byte code for SOperatorNode
311 	void generateByteCode(SOperatorNode node){
312 		// only 2 SOperators exist at this point, ref/de-ref, and `!`
313 		if (node.operator == "@"){
314 			// check if its being de-ref-ed
315 			if (node.operand.returnType.isRef){
316 				generateByteCode(node.operand);
317 				_writer.addInstruction(Instruction.Deref);
318 			}else if (node.operand.type == CodeNode.Type.Variable){
319 				_writer.addInstruction(Instruction.PushRefFrom, [to!string(node.operand.node!(CodeNode.Type.Variable).id)]);
320 			}else if (node.operand.type == CodeNode.Type.ReadElement){
321 				generateByteCode(node.operand.node!(CodeNode.Type.ReadElement), true);
322 			}
323 		}else if (node.operator == "!"){
324 			generateByteCode(node.operand);
325 			_writer.addInstruction(Instruction.Not);
326 		}
327 	}
328 	/// generates byte code for ReadElement
329 	void generateByteCode(ReadElement node, bool pushRef = false){
330 		generateByteCode(node.index);
331 		// the array should be a ref to array, following if-else takes care of that
332 		if (node.readFromNode.type == CodeNode.Type.ReadElement){
333 			generateByteCode(node.readFromNode.node!(CodeNode.Type.ReadElement), pushRef);
334 		}else if (node.readFromNode.type == CodeNode.Type.Variable){
335 			generateByteCode(node.readFromNode.node!(CodeNode.Type.Variable), true);
336 		}
337 		_writer.addInstruction(Instruction.ReadElement);
338 		if (!pushRef)
339 			_writer.addInstruction(Instruction.Deref);
340 	}
341 	/// generates byte code for VariableNode
342 	void generateByteCode(VariableNode node, bool pushRef = false){
343 		_writer.addInstruction(pushRef ? Instruction.PushRefFrom : Instruction.PushFrom, [to!string(node.id)]);
344 	}
345 	/// generates byte code for ArrayNode
346 	void generateByteCode(ArrayNode node){
347 		foreach (element; node.elements){
348 			generateByteCode(element);
349 		}
350 		_writer.addInstruction(Instruction.MakeArray);
351 	}
352 public:
353 	/// constructor
354 	this (){
355 		_writer = new NaByteCodeWriter();
356 	}
357 	/// destructor
358 	~this(){
359 		.destroy(_writer);
360 	}
361 	/// Returns: generated bytecode
362 	string[] getByteCode(){
363 		return _writer.getCode;
364 	}
365 	/// Returns: array of functions defined in script. The index is the function id (used to call function at runtime)
366 	Function[] getFunctionMap(){
367 		return _functions.dup;
368 	}
369 	/// generates byte code for ScriptNode
370 	void generateByteCode(ScriptNode node){
371 		_functions.length = node.functions.length;
372 		foreach (func; node.functions){
373 			generateByteCode(func);
374 		}
375 	}
376 }
377 
378 /// Used to generate byte code.
379 /// 
380 /// Code is really messy in here, needs improving
381 private class NaByteCodeWriter{
382 private:
383 	/// instructions of all functions
384 	List!(string[]) _generatedInstructions;
385 	/// arguments of all functions' instructions
386 	List!(string[][]) _generatedInstructionArgs;
387 	/// stackLength of each function
388 	List!uinteger _stackLengths;
389 	/// instructions of function currently being written to
390 	List!Instruction _currentInst;
391 	/// arguments of instructions of functions currently being written to
392 	List!(string[]) _currentInstArgs;
393 	/// number of elements used up on stack at a point
394 	uinteger _currentStackUsage;
395 	/// max number of elements used on on stack (reset to 0 before starting on a new function)
396 	uinteger _maxStackUsage;
397 
398 	/// called to update _maxStackUsageg if necessary
399 	void updateStackUsage(){
400 		if (_currentStackUsage > _maxStackUsage)
401 			_maxStackUsage = _currentStackUsage;
402 	}
403 public:
404 	/// Stores types of errors given by `this.addInstruction`
405 	enum ErrorType : ubyte{
406 		StackElementsInsufficient, /// the instructions needs to pop more elements than there are, on the stack
407 		ArgumentCountMismatch, /// instruction needs different number of arguemnts than provided
408 		NoError, /// there was no error
409 	}
410 	/// constructor
411 	this(){
412 		_generatedInstructions = new List!(string[]);
413 		_generatedInstructionArgs = new List!(string[][]);
414 		_currentInst = new List!Instruction;
415 		_currentInstArgs = new List!(string[]);
416 		_stackLengths = new List!uinteger;
417 	}
418 	/// destructor
419 	~this(){
420 		.destroy(_generatedInstructions);
421 		.destroy(_generatedInstructionArgs);
422 		.destroy(_currentInst);
423 		.destroy(_currentInstArgs);
424 		.destroy(_stackLengths);
425 	}
426 	/// Returns: generated byte code in a NaFunction[]
427 	string[] getCode(){
428 		string[] r;
429 		{
430 			uinteger l = 0;
431 			foreach (instArray; _generatedInstructions.toArray){
432 				l += instArray.length+1;
433 			}
434 			r.length = l;
435 		}
436 		uinteger writeTo = 0;
437 		foreach (i, instList; _generatedInstructions.toArray){
438 			r[writeTo] = "def "~to!string(_stackLengths.read(i));
439 			writeTo ++;
440 			string[][] args = _generatedInstructionArgs.read(i);
441 			foreach (index, inst; instList){
442 				string argStr = "";
443 				foreach (arg; args[index]){
444 					argStr ~= ' ' ~ arg;
445 				}
446 				r[writeTo] = to!string(inst) ~ argStr;
447 				writeTo++;
448 			}
449 		}
450 		return r;
451 	}
452 	/// Call to prepare writing a function
453 	void startFunction(uinteger argCount){
454 		// clear lists
455 		_currentInst.clear;
456 		_currentInstArgs.clear;
457 		// make space for arguments on stack
458 		_maxStackUsage = argCount;
459 		_currentStackUsage = argCount;
460 	}
461 	/// Call when a function has been completely written to.
462 	void appendFunction(){
463 		string[] instructions;
464 			instructions.length = _currentInst.length;
465 			Instruction[] inst = _currentInst.toArray;
466 			foreach (i, instruction; inst){
467 				instructions[i] = '\t'~instruction.to!string;
468 			}
469 		_generatedInstructions.append(instructions);
470 		_generatedInstructionArgs.append(_currentInstArgs.toArray);
471 		_stackLengths.append(_maxStackUsage);
472 		// reset
473 		_currentInst.clear;
474 		_currentInstArgs.clear;
475 		_maxStackUsage = 0;
476 		_currentStackUsage = 0;
477 	}
478 	/// Returns: nunmber of elements currently on stack
479 	@property uinteger stackLength(){
480 		return _currentStackUsage;
481 	}
482 	/// Returns: number of instructions
483 	@property uinteger instructionCount(){
484 		return _currentInst.length;
485 	}
486 	/// Adds an instruction
487 	///
488 	/// Returns: type of error if any, else, ErrorType.NoError
489 	ErrorType addInstruction(Instruction inst, string[] args = []){
490 		if (args.length != INSTRUCTION_ARG_COUNT[inst])
491 			return ErrorType.ArgumentCountMismatch;
492 		NaData[] argsNaData;
493 		argsNaData.length = args.length;
494 		foreach (i, arg; args){
495 			argsNaData[i] = readData(arg);
496 		}
497 		immutable uinteger popCount = instructionPopCount(inst, argsNaData);
498 		immutable uinteger pushCount = INSTRUCTION_PUSH_COUNT[inst];
499 		if (popCount > _currentStackUsage)
500 			return ErrorType.StackElementsInsufficient;
501 		_currentStackUsage -= popCount;
502 		_currentStackUsage += pushCount;
503 		updateStackUsage;
504 		_currentInst.append(inst);
505 		_currentInstArgs.append(args.dup);
506 		return ErrorType.NoError;
507 	}
508 	/// Changes argument of jump/jumpIf instruction at an index
509 	/// 
510 	/// Returns: true if done, false if not, usually because the instruction there isn't a jump/jumpIf
511 	bool changeJumpArg(uinteger index, uinteger newJumpIndex){
512 		if (index >= _currentInst.length || ! [Instruction.Jump, Instruction.JumpIf].hasElement(_currentInst.read(index)))
513 			return false;
514 		_currentInstArgs.set(index, [to!string(newJumpIndex)]);
515 		return true;
516 	}
517 }