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), false, true); 65 }else if (node.type == StatementNode.Type.If){ 66 generateByteCode(node.node!(StatementNode.Type.If)); 67 }else if (node.type == StatementNode.Type.VarDeclare){ 68 generateByteCode(node.node!(StatementNode.Type.VarDeclare)); 69 }else if (node.type == StatementNode.Type.While){ 70 generateByteCode(node.node!(StatementNode.Type.While)); 71 }else if (node.type == StatementNode.Type.Return){ 72 generateByteCode(node.node!(StatementNode.Type.Return)); 73 } 74 } 75 /// generates byte code for AssignmentNode 76 void generateByteCode(AssignmentNode node){ 77 // first get the value 78 generateByteCode(node.val); 79 if (node.indexes.length > 0){ 80 // use ReadElement to get the element to write to 81 for (integer i = node.indexes.length -1; i >= 0;i--){ 82 generateByteCode(node.indexes[i]); 83 } 84 _writer.addInstruction(node.deref ? Instruction.PushFrom : Instruction.PushRefFrom, [to!string(node.var.id)]); // this gets the ref ot array 85 // now add readElement for each of those 86 foreach (i; 0 .. node.indexes.length){ 87 _writer.addInstruction(Instruction.ArrayRefElement); 88 } 89 _writer.addInstruction(Instruction.WriteToRef); // and this writes value to ref 90 }else if (node.deref){ 91 _writer.addInstruction(Instruction.PushFrom, [to!string(node.var.id)]); // this gets the ref 92 _writer.addInstruction(Instruction.WriteToRef); // and this writes value to ref 93 }else{ 94 // just do pushTo 95 _writer.addInstruction(Instruction.WriteTo, [to!string(node.var.id)]); 96 } 97 } 98 /// generates ByteCode for DoWhileNode 99 void generateByteCode(DoWhileNode node){ 100 static uinteger jumpID = 0; 101 const uinteger currentJumpID = jumpID; 102 jumpID++; 103 // get the index of where the loop starts 104 _writer.addJumpPos("DoWhileStart"~currentJumpID.to!string); 105 // now comes loop body 106 generateByteCode(node.statement); 107 // condition 108 generateByteCode(node.condition); 109 _writer.addInstruction(Instruction.JumpIf, ["DoWhileStart"~currentJumpID.to!string]); 110 } 111 /// generates byte code for ForNode 112 void generateByteCode(ForNode node){ 113 static uinteger jumpID = 0; 114 const uinteger currentJumpID = jumpID; 115 jumpID++; 116 generateByteCode(node.initStatement); 117 // condition 118 _writer.addJumpPos("ForStart"~currentJumpID.to!string); 119 generateByteCode(node.condition); 120 _writer.addInstruction(Instruction.Not); 121 _writer.addInstruction(Instruction.JumpIf, ["ForEnd"~currentJumpID.to!string]); //placeholder, will write jumpToIndex later 122 // loop body 123 generateByteCode(node.statement); 124 // now the increment statement 125 generateByteCode(node.incStatement); 126 // jump back 127 _writer.addInstruction(Instruction.Jump, ["ForStart"~currentJumpID.to!string]); 128 _writer.addJumpPos("ForEnd"~currentJumpID.to!string); 129 } 130 /// generates byte code for FunctionCallNode 131 /// 132 /// pushRef is ignored 133 void generateByteCode(FunctionCallNode node, bool pushRef = false, bool popReturn = false){ 134 if (!node.isScriptDefined && node.isInBuilt){ 135 generateInBuiltFunctionByteCode(node, popReturn); 136 }else{ 137 // push args 138 foreach (arg; node.arguments){ 139 generateByteCode(arg); 140 } 141 _writer.addInstruction(node.isScriptDefined ? Instruction.ExecuteFunction : Instruction.ExecuteFunctionExternal, 142 [to!string(node.id), to!string(node.arguments.length)]); 143 if (popReturn) 144 _writer.addInstruction(Instruction.Pop); 145 } 146 } 147 /// generates byte code for inbuilt QScript functions (`length(void[])` and stuff) 148 void generateInBuiltFunctionByteCode(FunctionCallNode node, bool popReturn = false){ 149 /// argument types of function call 150 DataType[] argTypes; 151 argTypes.length = node.arguments.length; 152 foreach (i, arg; node.arguments){ 153 argTypes[i] = arg.returnType; 154 } 155 /// encoded name of function 156 string fName = encodeFunctionName(node.fName, argTypes); 157 /// stores if the instructions added push 1 element to stack, or zero (false) 158 bool pushesToStack = true; 159 /// length(@void[], int) 160 if (node.fName == "length"){ 161 if (matchArguments([DataType(DataType.Type.Void, 1, true), DataType(DataType.Type.Integer)],argTypes)){ 162 // set array length 163 generateByteCode(node.arguments[1]); // length comes first, coz it's popped later 164 generateByteCode(node.arguments[0]); 165 _writer.addInstruction(Instruction.ArrayLengthSet); 166 pushesToStack = false; 167 }else if (matchArguments([DataType(DataType.Type.Void, 1, false)], argTypes) || 168 matchArguments([DataType(DataType.Type.Char, 1, true)], argTypes)){ 169 // get array/string length 170 generateByteCode(node.arguments[0]); 171 _writer.addInstruction(Instruction.ArrayLength); 172 } 173 }else if (fName == encodeFunctionName("toInt", [DataType(DataType.Type.Char, 1)])){ 174 /// toInt(string) 175 generateByteCode(node.arguments[0]); 176 _writer.addInstruction(Instruction.StringToInt); 177 }else if (fName == encodeFunctionName("toInt", [DataType(DataType.Type.Double)])){ 178 /// toInt(double) 179 generateByteCode(node.arguments[0]); 180 _writer.addInstruction(Instruction.DoubleToInt); 181 }else if (fName == encodeFunctionName("toDouble", [DataType(DataType.Type.Char, 1)])){ 182 /// toDouble(string) 183 generateByteCode(node.arguments[0]); 184 _writer.addInstruction(Instruction.StringToDouble); 185 }else if (fName == encodeFunctionName("toDouble", [DataType(DataType.Type.Integer)])){ 186 /// toDouble(int) 187 generateByteCode(node.arguments[0]); 188 _writer.addInstruction(Instruction.IntToDouble); 189 }else if (fName == encodeFunctionName("toStr", [DataType(DataType.Type.Integer)])){ 190 /// toStr(int) 191 generateByteCode(node.arguments[0]); 192 _writer.addInstruction(Instruction.IntToString); 193 }else if (fName == encodeFunctionName("toStr", [DataType(DataType.Type.Double)])){ 194 /// toStr(double) 195 generateByteCode(node.arguments[0]); 196 _writer.addInstruction(Instruction.DoubleToString); 197 }else if (node.fName == "copy" && matchArguments( 198 [DataType(DataType.Type.Void, 1), DataType(DataType.Type.Void, 1, true)], argTypes)){ 199 // first push the argument 200 generateByteCode(node.arguments[0]); 201 _writer.addInstruction(Instruction.CopyArray, []); 202 generateByteCode(node.arguments[1]); 203 _writer.addInstruction(Instruction.WriteToRef, []); 204 pushesToStack = false; 205 } 206 if (pushesToStack && popReturn) 207 _writer.addInstruction(Instruction.Pop); 208 } 209 /// generates byte code for IfNode 210 void generateByteCode(IfNode node){ 211 static uinteger jumpID = 0; 212 const uinteger currentJumpID = jumpID; 213 jumpID++; 214 generateByteCode(node.condition); 215 _writer.addInstruction(Instruction.JumpIf, ["IfTrue"~currentJumpID.to!string]); 216 if (node.hasElse) 217 generateByteCode(node.elseStatement); 218 _writer.addInstruction(Instruction.Jump, ["IfEnd"~currentJumpID.to!string]); 219 _writer.addJumpPos("IfTrue"~currentJumpID.to!string); 220 generateByteCode(node.statement); 221 _writer.addJumpPos("IfEnd"~currentJumpID.to!string); 222 223 } 224 /// 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 225 void generateByteCode(VarDeclareNode node){ 226 foreach (varName; node.vars){ 227 if (node.hasValue(varName)) 228 generateByteCode(node.getValue(varName)); 229 else 230 _writer.addInstruction(Instruction.Push, [node.type == DataType(DataType.Type.Double) ? "0.0" : "0"]); 231 _writer.addInstruction(Instruction.WriteTo, [to!string(node.varIDs[varName])]); 232 } 233 } 234 /// generates byte code for WhileNode 235 void generateByteCode(WhileNode node){ 236 static uinteger jumpID = 0; 237 const uinteger currentJumpID = jumpID; 238 jumpID++; 239 immutable uinteger conditionIndex = _writer.instructionCount; 240 _writer.addJumpPos("WhileStart"~currentJumpID.to!string); 241 generateByteCode(node.condition); 242 _writer.addInstruction(Instruction.Not); 243 _writer.addInstruction(Instruction.JumpIf, ["WhileEnd"~currentJumpID.to!string]); 244 generateByteCode(node.statement); 245 _writer.addInstruction(Instruction.Jump, ["WhileStart"~currentJumpID.to!string]); 246 _writer.addJumpPos("WhileEnd"~currentJumpID.to!string); 247 } 248 /// generates byte code for ReturnNode 249 void generateByteCode(ReturnNode node){ 250 generateByteCode(node.value); 251 _writer.addInstruction(Instruction.ReturnVal); 252 // remember, return terminates execution in qscript, not in navm 253 _writer.addInstruction(Instruction.Terminate); 254 } 255 256 /// generates byte code for CodeNode 257 void generateByteCode(CodeNode node, bool pushRef = false){ 258 if (node.type == CodeNode.Type.FunctionCall){ 259 generateByteCode(node.node!(CodeNode.Type.FunctionCall), pushRef); 260 }else if (node.type == CodeNode.Type.Literal){ 261 generateByteCode(node.node!(CodeNode.Type.Literal), pushRef); 262 }else if (node.type == CodeNode.Type.Negative){ 263 generateByteCode(node.node!(CodeNode.Type.Negative), pushRef); 264 }else if (node.type == CodeNode.Type.Operator){ 265 generateByteCode(node.node!(CodeNode.Type.Operator), pushRef); 266 }else if (node.type == CodeNode.Type.SOperator){ 267 generateByteCode(node.node!(CodeNode.Type.SOperator), pushRef); 268 }else if (node.type == CodeNode.Type.ReadElement){ 269 generateByteCode(node.node!(CodeNode.Type.ReadElement), pushRef); 270 }else if (node.type == CodeNode.Type.Variable){ 271 generateByteCode(node.node!(CodeNode.Type.Variable), pushRef); 272 }else if (node.type == CodeNode.Type.Array){ 273 generateByteCode(node.node!(CodeNode.Type.Array), pushRef); 274 } 275 } 276 /// generates byte code for LiteralNode 277 /// 278 /// pushRef is ignored 279 void generateByteCode(LiteralNode node, bool pushRef = false){ 280 _writer.addInstruction(Instruction.Push, [node.literal]); 281 } 282 /// generates byte code for NegativeValueNode 283 /// 284 /// pushRef is ignored 285 void generateByteCode(NegativeValueNode node, bool pushRef = false){ 286 // push the value 287 generateByteCode(node.value, false); 288 // push the appropriate zero 289 _writer.addInstruction(Instruction.Push, [node.value.returnType.type == DataType.Type.Integer ? "0" : "0.0"]); 290 // now subtract 291 _writer.addInstruction(node.value.returnType.type == DataType.Type.Integer ? 292 Instruction.MathSubtractInt : Instruction.MathSubtractDouble); 293 } 294 /// generates byte code for OperatorNode 295 /// 296 /// pushRef is ignored 297 void generateByteCode(OperatorNode node, bool pushRef = false){ 298 bool isFloat = false; 299 if (node.operands[0].returnType == DataType(DataType.Type.Double) || 300 node.operands[1].returnType == DataType(DataType.Type.Double)){ 301 isFloat = true; 302 } 303 Instruction opInst; 304 switch (node.operator){ 305 case "/": 306 opInst = isFloat ? Instruction.MathDivideDouble : Instruction.MathDivideInt; 307 break; 308 case "*": 309 opInst = isFloat ? Instruction.MathMultiplyDouble : Instruction.MathMultiplyInt; 310 break; 311 case "+": 312 opInst = isFloat ? Instruction.MathAddDouble : Instruction.MathAddInt; 313 break; 314 case "-": 315 opInst = isFloat ? Instruction.MathSubtractDouble : Instruction.MathSubtractInt; 316 break; 317 case "%": 318 opInst = isFloat ? Instruction.MathModDouble : Instruction.MathModInt; 319 break; 320 case "<": 321 // the the end, just flip the oprands and use > (isGreater) because < doesnt exist in navm 322 opInst = isFloat ? Instruction.IsGreaterDouble : Instruction.IsGreaterInt; 323 break; 324 case ">": 325 opInst = isFloat ? Instruction.IsGreaterDouble : Instruction.IsGreaterInt; 326 break; 327 case "<=": 328 // the the end, just flip the oprands and use >= (isGreaterSame) because <= doesnt exist in navm 329 opInst = isFloat ? Instruction.IsGreaterSameDouble : Instruction.IsGreaterSameInt; 330 break; 331 case ">=": 332 opInst = isFloat ? Instruction.IsGreaterSameDouble : Instruction.IsGreaterSameInt; 333 break; 334 case "==": 335 opInst = node.operands[0].returnType.isArray ? Instruction.IsSameArray : Instruction.IsSame; 336 break; 337 case "&&": 338 opInst = Instruction.And; 339 break; 340 case "||": 341 opInst = Instruction.Or; 342 break; 343 case "~": 344 opInst = Instruction.Concatenate; 345 break; 346 default: 347 break; 348 } 349 // if operator is < or <=, then need to flip operands and change operator to > or >= 350 if (node.operator == "<" || node.operator == "<="){ 351 node.operator = node.operator == "<" ? ">" : ">="; 352 CodeNode operand = node.operands[0]; 353 node.operands[0] = node.operands[1]; 354 node.operands[1] = operand; 355 } 356 generateByteCode(node.operands[1]); 357 generateByteCode(node.operands[0]); 358 _writer.addInstruction(opInst); 359 } 360 /// generates byte code for SOperatorNode 361 /// 362 /// pushRef is ignored 363 void generateByteCode(SOperatorNode node, bool pushRef = false){ 364 // only 2 SOperators exist at this point, ref/de-ref, and `!` 365 if (node.operator == "@"){ 366 // check if its being de-ref-ed 367 if (node.operand.returnType.isRef){ 368 generateByteCode(node.operand); 369 _writer.addInstruction(Instruction.Deref,[]); 370 }else if (node.operand.type == CodeNode.Type.Variable){ 371 generateByteCode(node.operand, true); 372 }else if (node.operand.type == CodeNode.Type.ReadElement){ 373 generateByteCode(node.operand.node!(CodeNode.Type.ReadElement), true); 374 } 375 }else if (node.operator == "!"){ 376 generateByteCode(node.operand); 377 _writer.addInstruction(Instruction.Not); 378 } 379 } 380 /// generates byte code for ReadElement 381 void generateByteCode(ReadElement node, bool pushRef = false){ 382 generateByteCode(node.index); 383 // the array should be a ref to array, following if-else takes care of that 384 if (node.readFromNode.type == CodeNode.Type.ReadElement || node.readFromNode.type == CodeNode.Type.Variable){ 385 generateByteCode(node.readFromNode, true); 386 _writer.addInstruction(Instruction.ArrayRefElement); 387 }else{ 388 generateByteCode(node.readFromNode); 389 _writer.addInstruction(Instruction.ArrayElement); 390 } 391 if (!pushRef) 392 _writer.addInstruction(Instruction.Deref); 393 } 394 /// generates byte code for VariableNode 395 void generateByteCode(VariableNode node, bool pushRef = false){ 396 _writer.addInstruction(pushRef ? Instruction.PushRefFrom : Instruction.PushFrom, [to!string(node.id)]); 397 } 398 /// generates byte code for ArrayNode 399 /// 400 /// pushRef is ignored 401 void generateByteCode(ArrayNode node, bool pushRef = false){ 402 foreach (element; node.elements){ 403 generateByteCode(element); 404 } 405 _writer.addInstruction(Instruction.MakeArray, [node.elements.length.to!string]); 406 } 407 public: 408 /// constructor 409 this (){ 410 _writer = new NaByteCodeWriter(); 411 } 412 /// destructor 413 ~this(){ 414 .destroy(_writer); 415 } 416 /// Returns: generated bytecode 417 string[] getByteCode(){ 418 return _writer.getCode; 419 } 420 /// Returns: array of functions defined in script. The index is the function id (used to call function at runtime) 421 Function[] getFunctionMap(){ 422 return _functions.dup; 423 } 424 /// generates byte code for ScriptNode 425 void generateByteCode(ScriptNode node){ 426 _functions.length = node.functions.length; 427 foreach (func; node.functions){ 428 generateByteCode(func); 429 } 430 } 431 } 432 433 /// Used to generate byte code. 434 /// 435 /// Code is really messy in here, needs improving 436 private class NaByteCodeWriter{ 437 private: 438 /// instructions of all functions 439 List!(string[]) _generatedInstructions; 440 /// arguments of all functions' instructions 441 List!(string[][]) _generatedInstructionArgs; 442 /// stackLength of each function 443 List!uinteger _stackLengths; 444 /// instructions of function currently being written to 445 List!string _currentInst; 446 /// arguments of instructions of functions currently being written to 447 List!(string[]) _currentInstArgs; 448 /// number of elements used up on stack at a point 449 uinteger _currentStackUsage; 450 /// max number of elements used on on stack (reset to 0 before starting on a new function) 451 uinteger _maxStackUsage; 452 453 /// called to update _maxStackUsageg if necessary 454 void updateStackUsage(){ 455 if (_currentStackUsage > _maxStackUsage) 456 _maxStackUsage = _currentStackUsage; 457 } 458 public: 459 /// Stores types of errors given by `this.addInstruction` 460 enum ErrorType : ubyte{ 461 StackElementsInsufficient, /// the instructions needs to pop more elements than there are, on the stack 462 ArgumentCountMismatch, /// instruction needs different number of arguemnts than provided 463 NoError, /// there was no error 464 } 465 /// constructor 466 this(){ 467 _generatedInstructions = new List!(string[]); 468 _generatedInstructionArgs = new List!(string[][]); 469 _currentInst = new List!string; 470 _currentInstArgs = new List!(string[]); 471 _stackLengths = new List!uinteger; 472 } 473 /// destructor 474 ~this(){ 475 .destroy(_generatedInstructions); 476 .destroy(_generatedInstructionArgs); 477 .destroy(_currentInst); 478 .destroy(_currentInstArgs); 479 .destroy(_stackLengths); 480 } 481 /// Returns: generated byte code in a NaFunction[] 482 string[] getCode(){ 483 string[] r; 484 { 485 uinteger l = 0; 486 foreach (instArray; _generatedInstructions.toArray){ 487 l += instArray.length+1; 488 } 489 r.length = l; 490 } 491 uinteger writeTo = 0; 492 foreach (i, instList; _generatedInstructions.toArray){ 493 r[writeTo] = "def "~to!string(_stackLengths.read(i)); 494 writeTo ++; 495 string[][] args = _generatedInstructionArgs.read(i); 496 foreach (index, inst; instList){ 497 string argStr = ""; 498 foreach (arg; args[index]){ 499 argStr ~= ' ' ~ arg; 500 } 501 r[writeTo] = to!string(inst) ~ argStr; 502 writeTo++; 503 } 504 } 505 return r; 506 } 507 /// Call to prepare writing a function 508 void startFunction(uinteger argCount){ 509 // clear lists 510 _currentInst.clear; 511 _currentInstArgs.clear; 512 // make space for arguments on stack 513 _maxStackUsage = argCount; 514 _currentStackUsage = argCount; 515 } 516 /// Call when a function has been completely written to. 517 void appendFunction(){ 518 string[] instructions; 519 instructions.length = _currentInst.length; 520 string[] inst = _currentInst.toArray; 521 foreach (i, instruction; inst){ 522 instructions[i] = '\t'~instruction; 523 } 524 _generatedInstructions.append(instructions); 525 _generatedInstructionArgs.append(_currentInstArgs.toArray); 526 _stackLengths.append(_maxStackUsage); 527 // reset 528 _currentInst.clear; 529 _currentInstArgs.clear; 530 _maxStackUsage = 0; 531 _currentStackUsage = 0; 532 } 533 /// Returns: nunmber of elements currently on stack 534 @property uinteger stackLength(){ 535 return _currentStackUsage; 536 } 537 /// Returns: number of instructions 538 @property uinteger instructionCount(){ 539 return _currentInst.length; 540 } 541 /// Adds an instruction 542 /// 543 /// Returns: type of error if any, else, ErrorType.NoError 544 ErrorType addInstruction(Instruction inst, string[] args = []){ 545 if (args.length != INSTRUCTION_ARG_COUNT[inst]) 546 return ErrorType.ArgumentCountMismatch; 547 NaData[] argsNaData; 548 argsNaData.length = args.length; 549 foreach (i, arg; args){ 550 try{ 551 argsNaData[i] = readData(arg); 552 }catch (Exception e){ 553 argsNaData[i] = NaData(0); // its a ugly hack, i know, but it works... 554 } 555 } 556 immutable uinteger popCount = instructionPopCount(inst, argsNaData); 557 immutable uinteger pushCount = INSTRUCTION_PUSH_COUNT[inst]; 558 if (popCount > _currentStackUsage) 559 return ErrorType.StackElementsInsufficient; 560 _currentStackUsage -= popCount; 561 _currentStackUsage += pushCount; 562 updateStackUsage; 563 _currentInst.append(inst.to!string); 564 _currentInstArgs.append(args.dup); 565 return ErrorType.NoError; 566 } 567 /// Adds a jump position 568 void addJumpPos(string name){ 569 _currentInst.append(name~':'); 570 _currentInstArgs.append(cast(string[])[]); 571 } 572 }