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 }