1 /++
2 For generating byte code from AST
3 +/
4 module qscript.compiler.codegen;
5 
6 import qscript.compiler.ast;
7 import qscript.compiler.compiler;
8 import qscript.qscript : Library, QScriptBytecode;
9 
10 import navm.bytecode;
11 
12 import utils.misc;
13 import utils.lists;
14 
15 import std.conv : to;
16 
17 /// just an inherited NaBytecode that makes it easier to check if it failed to add some instruction
18 private class ByteCodeWriter : QScriptBytecode{
19 private:
20 	bool _errorFree;
21 public:
22 	this(NaInstruction[] instructionTable){
23 		super(instructionTable);
24 		_errorFree = true;
25 	}
26 	/// Returns: true if an error occurred, now, or previously
27 	override bool addInstruction(string instName, string argument){
28 		_errorFree = _errorFree && super.addInstruction(instName, argument);
29 		return _errorFree;
30 	}
31 	/// sets errorOccurred to false
32 	void resetError(){
33 		_errorFree = true;
34 	}
35 	/// if its error free
36 	@property bool errorFree(){
37 		return _errorFree;
38 	}
39 }
40 
41 /// Contains functions to generate NaByteCode from AST nodes
42 class CodeGen{
43 private:
44 	ByteCodeWriter _code;
45 	NaInstruction[] _instTable;
46 	ScriptNode _script;
47 	Library _scriptLib;
48 	Library[] _libs;
49 protected:
50 	/// Generates bytecode for FunctionNode
51 	void generateCode(FunctionNode node, CodeGenFlags flags = CodeGenFlags.None){
52 		_code.addJumpPos("__qscriptFunction"~node.id.to!string);
53 		if (node.name == "this"){
54 			// gotta do global variables too now
55 			foreach(varDecl; _script.variables)
56 				foreach (i; 0 .. varDecl.vars.length)
57 					_code.addInstruction("push", "0");
58 			foreach(varDecl; _script.variables)
59 				generateCode(varDecl);
60 		}else{
61 			// use jumpFrameN to adjust _stackIndex
62 			_code.addInstruction("push", node.arguments.length.to!string);
63 			_code.addInstruction("jumpFrameN", "__qscriptFunction"~node.id.to!string~"start");
64 			_code.addInstruction("jumpBack", "");
65 			_code.addJumpPos("__qscriptFunction"~node.id.to!string~"start");
66 		}
67 		// make space for variables
68 		foreach (i; 0 .. node.varStackCount)
69 			_code.addInstruction("push","0");
70 		generateCode(node.bodyBlock, CodeGenFlags.None);
71 		_code.addInstruction("jumpBack","");
72 	}
73 	/// generates bytecode for BlockNode
74 	void generateCode(BlockNode node, CodeGenFlags flags = CodeGenFlags.None){
75 		foreach (statement; node.statements)
76 			generateCode(statement, CodeGenFlags.None);
77 	}
78 	/// generates bytecode for CodeNode
79 	/// 
80 	/// `PushFunctionReturn` is set to flags regardless of value passed
81 	void generateCode(CodeNode node, CodeGenFlags flags = CodeGenFlags.None){
82 		flags |= CodeGenFlags.PushFunctionReturn;
83 		if (node.type == CodeNode.Type.Array){
84 			generateCode(node.node!(CodeNode.Type.Array), flags);
85 		}else if (node.type == CodeNode.Type.FunctionCall){
86 			generateCode(node.node!(CodeNode.Type.FunctionCall), flags);
87 		}else if (node.type == CodeNode.Type.Literal){
88 			generateCode(node.node!(CodeNode.Type.Literal), flags);
89 		}else if (node.type == CodeNode.Type.Negative){
90 			generateCode(node.node!(CodeNode.Type.Negative), flags);
91 		}else if (node.type == CodeNode.Type.Operator){
92 			generateCode(node.node!(CodeNode.Type.Operator), flags);
93 		}else if (node.type == CodeNode.Type.ReadElement){
94 			generateCode(node.node!(CodeNode.Type.ReadElement), flags);
95 		}else if (node.type == CodeNode.Type.SOperator){
96 			generateCode(node.node!(CodeNode.Type.SOperator), flags);
97 		}else if (node.type == CodeNode.Type.Variable){
98 			generateCode(node.node!(CodeNode.Type.Variable), flags);
99 		}else if (node.type == CodeNode.Type.MemberSelector){
100 			generateCode(node.node!(CodeNode.Type.MemberSelector), flags);
101 		}
102 	}
103 	/// generates bytecode for MemberSelectorNode
104 	/// 
105 	/// Valid flags:  
106 	/// * PushRef - only if node.type == Type.StructMemberRead
107 	void generateCode(MemberSelectorNode node, CodeGenFlags flags = CodeGenFlags.None){
108 		if (node.type == MemberSelectorNode.Type.EnumMemberRead){
109 			_code.addInstruction("push", node.memberNameIndex.to!string);
110 			return;
111 		}
112 		if (flags & CodeGenFlags.PushRef){
113 			_code.addInstruction("push",node.memberNameIndex.to!string);
114 			generateCode(node.parent, CodeGenFlags.None);
115 			_code.addInstruction("IncRef","");
116 		}else{
117 			// use the `arrayElement` from QScriptVM
118 			generateCode(node.parent, CodeGenFlags.None);
119 			_code.addInstruction("arrayElement", node.memberNameIndex.to!string);
120 		}
121 	}
122 	/// generates bytecode for VariableNode.
123 	/// 
124 	/// Valid flags:  
125 	/// * PushRef - only if node.type == Type.StructMemberRead
126 	void generateCode(VariableNode node, CodeGenFlags flags = CodeGenFlags.None){
127 		// check if local to script
128 		if (node.libraryId == -1){
129 			if (node.isGlobal){
130 				_code.addInstruction(flags & CodeGenFlags.PushRef ? "PushRefFromAbs" : "pushFromAbs", 
131 					node.id.to!string);
132 				return;
133 			}
134 			_code.addInstruction(flags & CodeGenFlags.PushRef ? "pushRefFrom" : "pushFrom", 
135 				node.id.to!string);
136 			return;
137 		}
138 		// try to use the library's own code generators if they exist
139 		Library lib = _libs[node.libraryId];
140 		if (flags & CodeGenFlags.PushRef && lib.generateVariableCode(_code,node.id, CodeGenFlags.PushRef))
141 			return;
142 		if (lib.generateVariableCode(_code, node.id, CodeGenFlags.None))
143 			return;
144 		// Fine, I'll do it myself
145 		_code.addInstruction("push", node.id.to!string);
146 		_code.addInstruction(flags & CodeGenFlags.PushRef ? "VarGetRef" : "VarGet",
147 			node.libraryId.to!string);
148 	}
149 	/// generates bytecode for ArrayNode.
150 	void generateCode(ArrayNode node, CodeGenFlags flags = CodeGenFlags.None){
151 		foreach (elem; node.elements)
152 			generateCode(elem, CodeGenFlags.None);
153 		_code.addInstruction("arrayFromElements", node.elements.length.to!string);
154 		if (flags & CodeGenFlags.PushRef)
155 			_code.addInstruction("pushRefFromPop","");
156 	}
157 	/// generates bytecode for LiteralNode.
158 	void generateCode(LiteralNode node, CodeGenFlags flags = CodeGenFlags.None){
159 		_code.addInstruction("push", node.literal); // ez
160 		if (flags & CodeGenFlags.PushRef)
161 			_code.addInstruction("pushRefFromPop","");
162 	}
163 	/// generates bytecode for NegativeValueNode.
164 	void generateCode(NegativeValueNode node, CodeGenFlags flags = CodeGenFlags.None){
165 		generateCode(node.value, CodeGenFlags.None);
166 		_code.addInstruction("push", "-1");
167 		if (node.value.returnType == DataType(DataType.Type.Int))
168 			_code.addInstruction("mathMultiplyInt", "");
169 		else if (node.value.returnType == DataType(DataType.Type.Double))
170 			_code.addInstruction("mathMultiplyDouble", "");
171 		else
172 			// this'll never happen, is checked for in ASTCheck
173 		if (flags & CodeGenFlags.PushRef)
174 			_code.addInstruction("pushRefFromPop","");
175 	}
176 	/// generates bytecode for OperatorNode.
177 	void generateCode(OperatorNode node, CodeGenFlags flags = CodeGenFlags.None){
178 		// just generator code for the function call
179 		generateCode(node.fCall, CodeGenFlags.PushFunctionReturn);
180 		if (flags & CodeGenFlags.PushRef)
181 			_code.addInstruction("pushRefFromPop","");
182 	}
183 	/// generates bytecode for SOperatorNode. 
184 	/// 
185 	/// Valid flags are:  
186 	/// * PushRef
187 	void generateCode(SOperatorNode node, CodeGenFlags flags = CodeGenFlags.None){
188 		// opRef is hardcoded
189 		if (node.operator == "@"){
190 			// dont care about flags, just get ref
191 			generateCode(node.operand, CodeGenFlags.PushRef);
192 			return;
193 		}
194 		// make sure only PushRef is passed, coz the function will see other flags too
195 		generateCode(node.fCall, cast(CodeGenFlags)(flags & CodeGenFlags.PushRef));
196 	}
197 	/// generates bytecode for ReadElement
198 	/// 
199 	/// Valid flags are:
200 	/// * pushRef
201 	void generateCode(ReadElement node, CodeGenFlags flags = CodeGenFlags.None){
202 		// if index is known, and it doesnt want ref, then there's a better way:
203 		if (node.index.type == CodeNode.Type.Literal && node.index.returnType == DataType(DataType.Type.Int) &&
204 		!(flags & CodeGenFlags.PushRef)){
205 			generateCode(node.readFromNode, CodeGenFlags.None);
206 			_code.addInstruction("arrayElement", node.index.node!(CodeNode.Type.Literal).literal);
207 			return;
208 		}
209 		// otherwise, use the multiple instructions method
210 		generateCode(node.index, CodeGenFlags.None);
211 		generateCode(node.readFromNode, CodeGenFlags.PushRef);
212 		_code.addInstruction("incRef", "");
213 		if (!(flags & CodeGenFlags.PushRef))
214 			_code.addInstruction("deref", "");
215 	}
216 	/// generates bytecode for StatementNode
217 	void generateCode(StatementNode node, CodeGenFlags flags = CodeGenFlags.None){
218 		if (node.type == StatementNode.Type.Assignment){
219 			generateCode(node.node!(StatementNode.Type.Assignment), flags);
220 		}else if (node.type == StatementNode.Type.Block){
221 			generateCode(node.node!(StatementNode.Type.Block), flags);
222 		}else if (node.type == StatementNode.Type.DoWhile){
223 			generateCode(node.node!(StatementNode.Type.DoWhile), flags);
224 		}else if (node.type == StatementNode.Type.For){
225 			generateCode(node.node!(StatementNode.Type.For), flags);
226 		}else if (node.type == StatementNode.Type.FunctionCall){
227 			generateCode(node.node!(StatementNode.Type.FunctionCall), flags);
228 		}else if (node.type == StatementNode.Type.If){
229 			generateCode(node.node!(StatementNode.Type.If), flags);
230 		}else if (node.type == StatementNode.Type.VarDeclare){
231 			generateCode(node.node!(StatementNode.Type.VarDeclare), flags);
232 		}else if (node.type == StatementNode.Type.While){
233 			generateCode(node.node!(StatementNode.Type.While), flags);
234 		}else if (node.type == StatementNode.Type.Return){
235 			generateCode(node.node!(StatementNode.Type.Return), flags);
236 		}
237 	}
238 	/// generates bytecode for VarDeclareNode
239 	void generateCode(VarDeclareNode node, CodeGenFlags flags = CodeGenFlags.None){
240 		// just push 0 or 0.0 to id
241 		foreach (i, varName; node.vars){
242 			if (node.hasValue(varName))
243 				generateCode(node.getValue(varName));
244 			else if ([DataType.Type.Int, DataType.Type.Bool, DataType.Type.Char].hasElement(node.type.type))
245 				_code.addInstruction("push", "0");
246 			else if (node.type.type == DataType.Type.Double)
247 				_code.addInstruction("push", "0.0");
248 			else if (node.type.type == DataType.Type.Custom && !node.type.isRef && !node.type.isArray){
249 				// create an array of length so members can be accomodated
250 				_code.addInstruction("makeArrayN", node.type.customLength.to!string);
251 			}
252 			_code.addInstruction("writeTo", node.varIDs[varName].to!string);
253 		}
254 	}
255 	/// generates bytecode for AssignmentNode
256 	void generateCode(AssignmentNode node, CodeGenFlags flags = CodeGenFlags.None){
257 		generateCode(node.rvalue, CodeGenFlags.None);
258 		generateCode(node.lvalue, node.deref ? CodeGenFlags.None : CodeGenFlags.PushRef);
259 		_code.addInstruction("writeToRef", "");
260 	}
261 	/// generates bytecode for IfNode
262 	void generateCode(IfNode node, CodeGenFlags flags = CodeGenFlags.None){
263 		static uinteger jumpCount = 0;
264 		uinteger currentJumpCount = jumpCount;
265 		jumpCount++;
266 		generateCode(node.condition, CodeGenFlags.None);
267 		_code.addInstruction("If", "");
268 		_code.addInstruction("Jump", "if"~currentJumpCount.to!string~"OnTrue");
269 		if (node.hasElse)
270 			generateCode(node.elseStatement, CodeGenFlags.None);
271 		_code.addInstruction("Jump", "if"~currentJumpCount.to!string~"End");
272 		_code.addJumpPos("if"~currentJumpCount.to!string~"OnTrue");
273 		generateCode(node.statement, CodeGenFlags.None);
274 		_code.addJumpPos("if"~currentJumpCount.to!string~"End");
275 	}
276 	/// generates bytecode for WhileNode
277 	void generateCode(WhileNode node, CodeGenFlags flags = CodeGenFlags.None){
278 		static uinteger jumpCount = 0;
279 		uinteger currentJumpCount = jumpCount;
280 		jumpCount++;
281 		// its less instructions if a modified do-while loop is used
282 		_code.addInstruction("Jump", "While"~currentJumpCount.to!string~"Condition");
283 		_code.addJumpPos("While"~currentJumpCount.to!string~"Start");
284 		generateCode(node.statement, CodeGenFlags.None);
285 		_code.addJumpPos("While"~currentJumpCount.to!string~"Condition");
286 		generateCode(node.condition);
287 		_code.addInstruction("If","");
288 		_code.addInstruction("Jump", "While"~currentJumpCount.to!string~"Start");
289 	}
290 	/// generates bytecode for DoWhileNode
291 	void generateCode(DoWhileNode node, CodeGenFlags flags = CodeGenFlags.None){
292 		static uinteger jumpCount = 0;
293 		uinteger currentJumpCount = jumpCount;
294 		jumpCount++;
295 		// its less instructions if a modified do-while loop is used
296 		_code.addJumpPos("DoWhile"~currentJumpCount.to!string~"Start");
297 		generateCode(node.statement, CodeGenFlags.None);
298 		generateCode(node.condition);
299 		_code.addInstruction("If","");
300 		_code.addInstruction("Jump", "DoWhile"~currentJumpCount.to!string~"Start");
301 	}
302 	/// generates bytecode for ForNode
303 	void generateCode(ForNode node, CodeGenFlags flags = CodeGenFlags.None){
304 		static uinteger jumpCount = 0;
305 		uinteger currentJumpCount = jumpCount;
306 		jumpCount++;
307 		generateCode(node.initStatement);
308 		_code.addInstruction("Jump", "For"~currentJumpCount.to!string~"Condition");
309 		_code.addJumpPos("For"~currentJumpCount.to!string~"Start");
310 		generateCode(node.statement);
311 		generateCode(node.incStatement);
312 		_code.addJumpPos("For"~currentJumpCount.to!string~"Condition");
313 		generateCode(node.condition);
314 		_code.addInstruction("If", "");
315 		_code.addInstruction("Jump", "For"~currentJumpCount.to!string~"Start");
316 	}
317 	/// generates bytecode for FunctionCallNode
318 	void generateCode(FunctionCallNode node, CodeGenFlags flags = CodeGenFlags.None){
319 		uinteger[] argOrder = node.libraryId>-1 ? _libs[node.libraryId].functionCallArgumentsPushOrder(node.id) :[];
320 		if (argOrder.length && argOrder.length == node.arguments.length){
321 			foreach (argIndex; argOrder)
322 				generateCode(node.arguments[argIndex]);
323 		}else{
324 			foreach (arg; node.arguments)
325 				generateCode(arg);
326 		}
327 		// if a local call, just use JumpFrameN
328 		if (node.libraryId == -1){
329 			_code.addInstruction("jumpFrameN", node.arguments.length.to!string);
330 		}else{
331 			// try to generate it's code, if no, then just use Call
332 			Library lib = _libs[node.libraryId];
333 			if (lib.generateFunctionCallCode(_code, node.id, flags))
334 				return;
335 			_code.addInstruction("push", node.id.to!string);
336 			_code.addInstruction("push", node.libraryId.to!string);
337 			_code.addInstruction("Call", (node.arguments.length+2).to!string);
338 		}
339 		if (flags & CodeGenFlags.PushFunctionReturn){
340 			_code.addInstruction("retValPush", "");
341 			if (flags & CodeGenFlags.PushRef && !node.returnType.isRef)
342 				_code.addInstruction("pushRefFromPop", "");
343 		}
344 	}
345 	/// generates bytecode for ReturnNode
346 	void generateCode(ReturnNode node, CodeGenFlags flags = CodeGenFlags.None){
347 		generateCode(node.value);
348 		_code.addInstruction("retValSet", "");
349 		_code.addInstruction("jumpBack", "");
350 	}
351 public:
352 	/// constructor
353 	this(Library[] libraries, NaInstruction[] instructionTable){
354 		_libs = libraries;
355 		_instTable = instructionTable;
356 	}
357 	~this(){
358 	}
359 	/// generates byte code for a ScriptNode. Use CodeGen.bytecode to get the generated bytecode
360 	/// 
361 	/// `node` is the ScriptNode to generate bytecode for  
362 	/// `scriptLibrary` is the private & public declarations of the script (allDeclarations from `ASTCheck.checkAST(..,x)`)
363 	/// 
364 	/// Returns: true if successfully generated, false if there were errors
365 	bool generateCode(ScriptNode node, Library scriptLibrary){
366 		_code = new ByteCodeWriter(_instTable);
367 		_script = node;
368 		_scriptLib = scriptLibrary;
369 		// make space for jumps for function calls
370 		foreach (i; 0 .. _script.functions.length)
371 			_code.addInstruction("jump", "__qscriptFunction"~i.to!string);
372 		foreach (func; _script.functions)
373 			generateCode(func);
374 		// put link info on it
375 		_code.linkInfo = _scriptLib.toString;
376 		return _code.errorFree;
377 	}
378 	/// Returns: generated bytecode
379 	@property QScriptBytecode bytecode(){
380 		return _code;
381 	}
382 }