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 }