1 module navm.bytecode; 2 3 import utils.ds; 4 import utils.misc; 5 6 import std.conv : to; 7 8 /// To store a single line of bytecode. This is used for raw bytecode. 9 public struct Statement{ 10 /// label, if any, otherwise, null or empty string 11 string label; 12 /// instruction name 13 string instName; 14 /// arguments, if any. These are ignored if `instName.length == 0` 15 string[] arguments; 16 /// comment, if any 17 string comment; 18 /// postblit 19 this(this){ 20 this.label = this.label.dup; 21 this.instName = this.instName.dup; 22 this.arguments = this.arguments.dup; 23 this.comment = this.comment.dup; 24 } 25 /// constructor, for instruction + args + comment 26 this(string instName, string[] arguments=[], string comment = ""){ 27 this.instName = instName; 28 this.arguments = arguments.dup; 29 this.comment = comment; 30 } 31 /// constructor, for label + instruction 32 this(string label, string instName, string[] arguments=[], string comment = ""){ 33 this.label = label; 34 this.instName = instName; 35 this.arguments = arguments.dup; 36 this.comment = comment; 37 } 38 /// Reads statement from string 39 void fromString(string statement){ 40 label = ""; 41 instName = ""; 42 arguments.length = 0; 43 string[] separated = statement.separateWhitespace(); 44 if (separated.length == 0) 45 return; 46 if (separated[0].length && separated[0][$-1] == ':'){ 47 this.label = separated[0][0 .. $-1]; 48 separated = separated[1 .. $]; 49 if (separated.length == 0) 50 return; 51 } 52 if (separated[0].isAlphabet){ 53 this.instName = separated[0]; 54 separated = separated[1 .. $]; 55 } 56 if (separated.length) 57 this.arguments = separated; 58 } 59 /// Returns: string representation of this statement 60 string toString(){ 61 string r; 62 if (label.length) 63 r = label ~ ": "; 64 r ~= instName; 65 foreach (arg; arguments) 66 r ~= ' ' ~ arg; 67 if (comment.length) 68 r ~= "#"~comment; 69 return r; 70 } 71 } 72 /// 73 unittest{ 74 Statement s; 75 s.fromString("someLabel: someInst arg1 arg2#comment"); 76 assert(s == Statement("someLabel", "someInst", ["arg1", "arg2"])); 77 s.fromString("someInst arg1 # comment"); 78 assert(s == Statement("someInst", ["arg1"]), s.toString); 79 s.fromString("load 0"); 80 assert(s == Statement("load", ["0"])); 81 } 82 83 /// stores an data about available instruction 84 public struct NaInst{ 85 /// name of instruction, **in lowercase** 86 string name; 87 /// value when read as a ushort; 88 ushort code = 0x0000; 89 /// what type of arguments are expected 90 NaInstArgType[] arguments; 91 /// constructor 92 this (string name, uinteger code, NaInstArgType[] arguments = []){ 93 this.name = name; 94 this.code = cast(ushort)code; 95 this.arguments = arguments.dup; 96 } 97 /// constructor 98 this (string name, NaInstArgType[] arguments = []){ 99 this.name = name; 100 this.code = 0; 101 this.arguments = arguments.dup; 102 } 103 } 104 105 /// Types of instruction arguments, for validation 106 public enum NaInstArgType : ubyte{ 107 Integer, /// singed integer (ptrdiff_t) 108 Double, /// a double (float) 109 Address, /// Address to some argument 110 String, /// a string (char[]) 111 Label, /// a label (name is stored) 112 Char, /// a 1 byte character 113 Boolean, /// boolean 114 } 115 116 /// For storing argument that is an Address 117 public struct NaInstArgAddress{ 118 string labelOffset; /// label, if any 119 uinteger address; /// the address itself 120 /// constructor 121 this (string labelOffset, uinteger address = 0){ 122 this.labelOffset = labelOffset; 123 this.address = address; 124 } 125 /// ditto 126 this (uinteger address){ 127 this.address = address; 128 } 129 /// ditto 130 private this(ubyte[] binaryData){ 131 this._binData = binaryData; 132 } 133 /// Returns: this address, when stored as stream of bytes 134 private @property ubyte[] _binData(){ 135 ubyte[] r; 136 r.length = labelOffset.length + 8; 137 r[] = 0; 138 ByteUnion!uinteger u = ByteUnion!uinteger(address); 139 debug{assert (u.array.length <= 8);} 140 r[0 .. u.array.length] = u.array; 141 r[8 .. $] = cast(ubyte[])labelOffset; 142 return r; 143 } 144 /// ditto 145 private @property ubyte[] _binData(ubyte[] newVal){ 146 labelOffset = []; 147 address = 0; 148 debug{assert (newVal.length >= 8);} 149 immutable ByteUnion!uinteger u = ByteUnion!uinteger(newVal[0 .. uinteger.sizeof]); 150 address = u.data; 151 if (newVal.length > 8) 152 labelOffset = cast(string)newVal[8 .. $].dup; 153 return newVal; 154 } 155 } 156 157 /// For storing data of varying data types 158 public struct NaData{ 159 /// the actual data 160 ubyte[] argData; 161 /// value. ** do not use this for arrays, aside from string ** 162 /// 163 /// Returns: stored value, or `T.init` if invalid type 164 @property T value(T)(){ 165 static if (is (T == string)) 166 return cast(string)cast(char[])argData; 167 else static if (is (T == NaInstArgAddress)) 168 return NaInstArgAddress(argData); 169 else if (argData.length < T.sizeof) 170 return T.init; 171 else 172 return *(cast(T*)argData.ptr); 173 } 174 /// ditto 175 @property T value(T)(T newVal){ 176 static if (is (T == string)){ 177 argData.length = newVal.length; 178 argData = cast(ubyte[])(cast(char[])newVal.dup); 179 return newVal; 180 }else static if (is (T == NaInstArgAddress)){ 181 argData = newVal._binData; 182 return newVal; 183 }else if (argData.length >= T.sizeof){ 184 argData[0 .. T.sizeof] = (cast(ubyte*)&newVal)[0 .. T.sizeof]; 185 return newVal; 186 }else{ 187 argData.length = T.sizeof; 188 return this.value!T = newVal; 189 } 190 } 191 /// constructor 192 this(T)(T value){ 193 this.value!T = value; 194 } 195 } 196 /// 197 unittest{ 198 assert(NaData(cast(integer)1025).value!integer == 1025); 199 assert(NaData("hello").value!string == "hello"); 200 assert(NaData(cast(double)50.5).value!double == 50.5); 201 assert(NaData('a').value!char == 'a'); 202 assert(NaData(true).value!bool == true); 203 } 204 205 /// Stores bytecode 206 public class NaBytecode{ 207 private: 208 ushort[] _instCodes; /// codes of instructions 209 NaData[] _instArgs; /// instruction arguments 210 NaInstArgType[] _instArgTypes; /// instruction argument types 211 uinteger[2][] _labelIndexes; /// [codeIndex, argIndex] for each label index 212 string[] _labelNames; /// label names 213 NaInstTable _instTable; /// the instruction table 214 bool _lastWasArgOnly; /// if last statement appended was args without instName 215 protected: 216 /// Returns: size in bytes of argument at an index 217 uinteger argSize(uinteger argIndex){ 218 if (argIndex > _instArgs.length) 219 return 0; 220 if (_instArgTypes[argIndex] == NaInstArgType.Address || _instArgTypes[argIndex] == NaInstArgType.Integer || 221 _instArgTypes[argIndex] == NaInstArgType.Label) 222 return integer.sizeof; 223 else if (_instArgTypes[argIndex] == NaInstArgType.Boolean || _instArgTypes[argIndex] == NaInstArgType.Char) 224 return 1; 225 else if (_instArgTypes[argIndex] == NaInstArgType.Double) 226 return double.sizeof; 227 else if (_instArgTypes[argIndex] == NaInstArgType.String) 228 return _instArgs[argIndex].value!string.length + integer.sizeof; 229 return 0; 230 } 231 /// Changes labels to label indexes, and resolves addresses, in arguments 232 /// 233 /// called by this.verify 234 /// 235 /// Returns: false if there are invalid labels or addresses 236 bool resolveArgs(){ 237 foreach (i; 0 .. _instArgs.length){ 238 // addresses: for now, change it to argument index. 239 if (_instArgTypes[i] == NaInstArgType.Address){ 240 NaInstArgAddress addr = _instArgs[i].value!NaInstArgAddress; 241 if (addr.labelOffset.length){ 242 immutable integer index = _labelNames.indexOf(addr.labelOffset); 243 if (index == -1) 244 return false; 245 addr.address += _labelIndexes[index][1]; // TODO 246 _instArgs[i].value!NaInstArgAddress = addr; 247 } 248 if (addr.address >= _instArgs.length) 249 return false; 250 }else if (_instArgTypes[i] == NaInstArgType.Label){ 251 // change label to label index 252 immutable integer index = _labelNames.indexOf(_instArgs[i].value!string); 253 if (index == -1) 254 return false; 255 _instArgs[i].value!integer = index; 256 } 257 } 258 // now change addresses from indexes to addresses 259 for(uinteger i, labelIndex, size; i < _instArgs.length; i ++){ 260 if (_instArgTypes[i] == NaInstArgType.Address){ 261 uinteger addressVal = 0; 262 foreach (argIndex; 0 .. _instArgs[i].value!integer) 263 addressVal += argSize(argIndex); 264 _instArgs[i].value!integer = addressVal; 265 } 266 // if a label has this arg, change that too, this works assuming labels are in sorted order 267 while (labelIndex < _labelIndexes.length && _labelIndexes[labelIndex][1] == i){ 268 _labelIndexes[labelIndex][1] = size; 269 labelIndex ++; 270 } 271 size += argSize(i); 272 } 273 return true; 274 } 275 public: 276 /// constructor 277 this(NaInstTable instructionTable){ 278 this._instTable = instructionTable; 279 _lastWasArgOnly = false; 280 } 281 ~this(){} 282 /// Returns: instruction codes 283 @property ushort[] instCodes(){ 284 return _instCodes; 285 } 286 /// Returns: array of instruction pointers. invalid instructions will have null ptr 287 @property void delegate()[] instPtrs(){ 288 void delegate()[] r; 289 r.length = _instCodes.length; 290 foreach (i; 0 .. r.length){ 291 try 292 r[i] = _instTable.getInstructionPtr(_instCodes[i]); 293 catch (Exception e){ 294 .destroy(e); 295 r[i] = null; 296 } 297 } 298 return r; 299 } 300 /// Returns: arguments for instructions 301 @property NaData[] instArgs(){ 302 return _instArgs; 303 } 304 /// Returns: types of arguments for instructions 305 @property NaInstArgType[] instArgTypes(){ 306 return _instArgTypes; 307 } 308 /// Returns: label indexes (`[instructionIndex, argIndex]`) 309 @property uinteger[2][] labelIndexes(){ 310 return _labelIndexes; 311 } 312 /// Returns: label names, corresponding to labelIndexes 313 @property string[] labelNames(){ 314 return _labelNames; 315 } 316 /// Discards any existing bytecode 317 void clear(){ 318 _instCodes.length = 0; 319 _instArgs.length = 0; 320 _labelIndexes.length = 0; 321 _labelNames.length = 0; 322 _lastWasArgOnly = false; 323 } 324 /// Verifies a loaded bytecode, to make sure only valid instructions exist, and correct number of arguments and types are loaded 325 /// No more statements should be added after this has been called 326 /// 327 /// Returns: true if verified without errors, false if there were errors. 328 bool verify(string error){ 329 if (_labelNames.length != _labelIndexes.length || _instArgTypes.length != _instArgs.length){ 330 error = "labelNames and labelIndexes, and/or argumentTypes and arguments length mismatch"; 331 return false; 332 } 333 uinteger argsInd; 334 foreach (i; 0 .. _instCodes.length){ 335 NaInst inst; 336 try 337 inst = _instTable.getInstruction(_instCodes[i]); 338 catch (Exception e){ 339 .destroy(e); 340 error = "instruction with code "~_instCodes[i].to!string~" does not exist"; 341 return false; 342 } 343 if (_instArgs.length < argsInd || _instArgs.length - argsInd < inst.arguments.length){ 344 error = "not enough arguments for instruction `"~inst.name~"` with code "~inst.code.to!string; 345 return false; 346 } 347 foreach (argInd; argsInd .. argsInd + inst.arguments.length){ 348 if (_instArgTypes[argInd] != inst.arguments[argInd - argsInd]){ 349 error = "invalid argument types for instruction `"~inst.name~"` with code "~inst.code.to!string; 350 return false; 351 } 352 } 353 argsInd += inst.arguments.length; 354 } 355 return resolveArgs(); 356 } 357 /// ditto 358 bool verify(){ 359 string dummyError; 360 return verify(dummyError); 361 } 362 /// Adds a statement at end of existing bytecode 363 /// 364 /// Returns: true if no errors, false if not done due to errors 365 bool append(Statement statement, ref string error){ 366 if (statement.label.length){ 367 if (_labelNames.hasElement(statement.label.lowercase)){ 368 error = "label `" ~ statement.label ~ "` used multiple times"; 369 return false; 370 } 371 _labelIndexes ~= [_instCodes.length, _instArgs.length]; 372 _labelNames ~= statement.label.lowercase; 373 } 374 if (statement.instName.length + statement.arguments.length == 0) 375 return true; 376 if (statement.instName.length && _lastWasArgOnly){ 377 error = "cannot add instruction after data section"; 378 return false; 379 } 380 if (!statement.instName.length && statement.arguments.length) 381 _lastWasArgOnly = true; 382 NaData[] args; 383 NaInstArgType[] types; 384 args.length = statement.arguments.length; 385 types.length = args.length; 386 foreach (index, arg; statement.arguments){ 387 try{ 388 args[index] = readData(arg, types[index]); 389 }catch (Exception e){ 390 error ~= "argument `"~arg~"`: "~e.msg; 391 .destroy(e); 392 return false; 393 } 394 } 395 if (statement.instName.length){ 396 immutable integer code = _instTable.getInstruction(statement.instName.lowercase, types); 397 if (code == -1){ 398 error = "instruction does not exist or invalid arguments"; 399 return false; 400 } 401 _instCodes ~= cast(ushort)code; 402 } 403 _instArgs ~= args; 404 _instArgTypes ~= types; 405 return true; 406 } 407 /// ditto 408 bool append(Statement statement){ 409 string error; 410 return this.append(statement, error); 411 } 412 /// ditto 413 bool append(string statementStr, ref string error){ 414 Statement statement; 415 statement.fromString(statementStr); 416 return this.append(statement, error); 417 } 418 /// ditto 419 bool append(string statementStr){ 420 Statement statement; 421 statement.fromString(statementStr); 422 return this.append(statement); 423 } 424 /// Loads bytecode. Discards any existing bytecode 425 /// 426 /// Returns: [] if done without errors. error descriptions if there were errors 427 string[] load(Statement[] statements){ 428 this.clear(); 429 statements = statements.dup; 430 string[] errors; 431 foreach (i, statement; statements){ 432 string error; 433 if (!append(statement, error)) 434 errors ~= "line "~(i+1).to!string~": "~error; 435 } 436 return errors; 437 } 438 /// ditto 439 string[] load(string[] statementStrings){ 440 Statement[] statements; 441 statements.length = statementStrings.length; 442 foreach (i, line; statementStrings) 443 statements[i].fromString(line); 444 return load(statements); 445 } 446 } 447 /// 448 unittest{ 449 string[] source = [ 450 "start: instA l2", 451 " instB 50 50.5", 452 " instC \"hello\" false", 453 "l2: instd 'c' start" 454 ]; 455 NaInstTable iTable = new NaInstTable(); 456 NaInst inst = NaInst("insta",[NaInstArgType.Label]); 457 iTable.addInstruction(inst); 458 inst = NaInst("instb", [NaInstArgType.Integer, NaInstArgType.Double]); 459 iTable.addInstruction(inst); 460 inst = NaInst("instc", [NaInstArgType.String, NaInstArgType.Boolean]); 461 iTable.addInstruction(inst); 462 inst = NaInst("instd", [NaInstArgType.Char, NaInstArgType.Label]); 463 iTable.addInstruction(inst); 464 NaBytecode bcode = new NaBytecode(iTable); 465 bcode.load(source); 466 assert(bcode.labelNames == ["start", "l2"]); 467 assert(bcode.labelIndexes == [[0, 0], [3, 5]]); 468 assert(bcode.instArgTypes == [NaInstArgType.Label, NaInstArgType.Integer, NaInstArgType.Double, NaInstArgType.String, 469 NaInstArgType.Boolean, NaInstArgType.Char, NaInstArgType.Label]); 470 assert(bcode.verify == true); 471 .destroy(iTable); 472 .destroy(bcode); 473 } 474 475 /// same as NaBytecode, but also works with binary bytecode (see `spec/binarybytecode.md`) 476 public class NaBytecodeBinary : NaBytecode{ 477 private: 478 /// magic number 479 const ubyte[7] MAGIC_NUM = cast(ubyte[7])"NAVMBC-"; 480 /// version bytes 481 const ushort SIG_VER = 0x0001; 482 /// number of bytes after magic bytes+version bytes to ignore 483 const ubyte MAGIC_BYTES_IGNORE = 8; 484 485 ByteStream _bin; 486 ubyte[] _sig; 487 ubyte[] _metadata; 488 public: 489 /// constructor 490 this(NaInstTable instructionTable, ubyte[] magicNumberPost){ 491 super(instructionTable); 492 this.magicNumberPost = magicNumberPost; 493 _bin = new ByteStream(); 494 } 495 ~this(){ 496 .destroy(_bin); 497 } 498 /// postfix for magic number 499 @property ubyte[] magicNumberPost(){ 500 return _sig.dup; 501 } 502 /// postfix for magic number 503 /// If the newVal is too long, the first bytes are used. If too short, 0x00 is used to fill 504 @property ubyte[] magicNumberPost(ubyte[] newVal){ 505 _sig.length = MAGIC_BYTES_IGNORE; 506 _sig[] = 0x00; 507 immutable uinteger len = newVal.length > MAGIC_BYTES_IGNORE ? MAGIC_BYTES_IGNORE : newVal.length; 508 _sig[0 .. len] = newVal[0 .. len]; 509 return _sig.dup; 510 } 511 /// the metadata stored alongside 512 @property ubyte[] metadata(){ 513 return _metadata; 514 } 515 /// ditto 516 @property ubyte[] metadata(ubyte[] newVal){ 517 return _metadata = newVal; 518 } 519 /// the ByteStream storing bytecode. Be aware that this will be destroyed when NaBytecodeBinary is destroyed 520 @property ByteStream binCode(){ 521 return _bin; 522 } 523 /// Prepares binary bytecode. **call .verify() before this** 524 void writeBinCode(){ 525 _bin.size = 0; 526 _bin.grow = true; 527 _bin.maxSize = 0; 528 // start by signature 529 _bin.writeRaw(MAGIC_NUM); 530 _bin.write(cast(ushort)SIG_VER); 531 _bin.writeRaw(_sig); 532 // metadata 533 _bin.writeArray(_metadata, 8); 534 // instruction codes 535 _bin.writeArray(_instCodes, 8); 536 // args 537 _bin.write(_instArgs.length, 8); /// number of args 538 foreach (i, arg; _instArgs){ 539 _bin.write!ubyte(_instArgTypes[i], 1); 540 _bin.writeArray(arg.argData, 8); 541 } 542 // labels 543 _bin.write(_labelIndexes.length, 8); // number of labels 544 foreach (i, label; _labelIndexes){ 545 _bin.write!uinteger(label[0], 8); // code index 546 _bin.write!uinteger(label[1], 8); // instruction index 547 _bin.writeArray(_labelNames[i], 8); // name 548 } 549 } 550 /// Reads binary bytecode. Any existing bytecode is `clear()`ed 551 /// 552 /// Returns: true on success, false if file is malformed 553 bool readBinCode(){ 554 this.clear; 555 _metadata = []; 556 _sig = []; 557 if (_bin.size <= MAGIC_NUM.length + SIG_VER.sizeof + MAGIC_BYTES_IGNORE) 558 return false; 559 ubyte[] buffer; 560 uinteger readCount; 561 bool incompleteRead; 562 buffer.length = MAGIC_NUM.length; 563 _bin.seek=0; 564 if (_bin.readRaw(buffer) != buffer.length || buffer != MAGIC_NUM) 565 return false; 566 if (_bin.read!ushort != SIG_VER) 567 return false; 568 _sig.length = MAGIC_BYTES_IGNORE; 569 _bin.readRaw(_sig); 570 // read metadata 571 _metadata = _bin.readArray!ubyte(readCount, 8); 572 if (readCount < _metadata.length) 573 return false; 574 // instruction codes 575 _instCodes = _bin.readArray!ushort(readCount, 8); 576 if (readCount < _instCodes.length) 577 return false; 578 // arguments 579 _instArgs.length = _bin.read!uinteger(incompleteRead,8); 580 if (incompleteRead) 581 return false; 582 _instArgTypes.length = _instArgs.length; 583 foreach(i; 0 .. _instArgs.length){ 584 _instArgTypes[i] = _bin.read!(NaInstArgType)(incompleteRead,1); 585 instArgs[i].argData = _bin.readArray!ubyte(readCount, 8); 586 if (readCount < instArgs[i].argData.length) 587 return false; 588 } 589 // labels 590 _labelIndexes.length = _bin.read!uinteger(incompleteRead,8); 591 if (incompleteRead) 592 return false; 593 _labelNames.length = _labelIndexes.length; 594 foreach (i; 0 .. _labelIndexes.length){ 595 _labelIndexes[i][0] = _bin.read!uinteger(incompleteRead, 8); 596 if (incompleteRead) 597 return false; 598 _labelIndexes[i][1] = _bin.read!uinteger(incompleteRead, 8); 599 if (incompleteRead) 600 return false; 601 _labelNames[i] = cast(string)_bin.readArray!char(readCount, 8); 602 if (readCount < _labelNames[i].length) 603 return false; 604 } 605 return true; 606 } 607 } 608 /// 609 unittest{ 610 NaInstTable iTable = new NaInstTable(); 611 NaInst inst = NaInst("insta",[NaInstArgType.Label]); 612 iTable.addInstruction(inst); 613 inst = NaInst("instb", [NaInstArgType.Integer, NaInstArgType.Double]); 614 iTable.addInstruction(inst); 615 inst = NaInst("instc", [NaInstArgType.String, NaInstArgType.Boolean]); 616 iTable.addInstruction(inst); 617 inst = NaInst("instd", [NaInstArgType.Char, NaInstArgType.Label]); 618 iTable.addInstruction(inst); 619 NaBytecodeBinary binCode = new NaBytecodeBinary(iTable, cast(ubyte[])"test"); 620 bool status = true; 621 status = status && binCode.append("start: instA end"); 622 status = status && binCode.append("instB 1025 1025.5"); 623 status = status && binCode.append("instc \"tab:\\tnewline:\\n\" true"); 624 status = status && binCode.append("end: instD 'c' start"); 625 assert(status == true); // all those functions returned true 626 627 binCode.metadata = cast(ubyte[])"METADATA-metadata-0123456789"; 628 assert(binCode.verify()); 629 binCode.writeBinCode(); 630 binCode.binCode.toFile("tempcode"); 631 binCode.binCode.size = 0; 632 binCode.metadata = []; 633 binCode.binCode.fromFile("tempcode"); 634 assert(binCode.readBinCode() == true); 635 assert(binCode.metadata == cast(ubyte[])"METADATA-metadata-0123456789"); 636 assert(binCode.instCodes == [1,2,3,4]); 637 assert(binCode.instArgTypes == [NaInstArgType.Label, NaInstArgType.Integer, NaInstArgType.Double, 638 NaInstArgType.String, NaInstArgType.Boolean, NaInstArgType.Char, NaInstArgType.Label]); 639 assert(binCode.instArgs[0].value!integer == binCode.labelNames.indexOf("end")); 640 assert(binCode.instArgs[1].value!integer == 1025); 641 assert(binCode.instArgs[2].value!double == 1025.5); 642 assert(binCode.instArgs[3].value!string == "tab:\tnewline:\n"); 643 assert(binCode.instArgs[4].value!bool == true); 644 assert(binCode.instArgs[5].value!char == 'c'); 645 assert(binCode.instArgs[6].value!integer == binCode.labelNames.indexOf("start")); 646 assert(binCode.labelNames == ["start", "end"]); 647 .destroy(binCode); 648 .destroy(iTable); 649 } 650 651 /// Stores an instruction table 652 public class NaInstTable{ 653 private: 654 NaInst[ushort] _instructions; /// avaliable instructions. index is code 655 void delegate()[ushort] _instPtrs; /// pointers for instruction codes 656 public: 657 /// constructor 658 this(){ 659 660 } 661 /// destructor 662 ~this(){} 663 /// Adds a new instruction. 664 /// If `inst.code == 0`, Finds an available code, assigns `inst.code` that code. 665 /// Otherwise `inst.code` is used, if available, or -1 returned. 666 /// 667 /// Returns: instruction code if success, or -1 in case of error 668 /// Error can be: code!=0 and code already used. No more codes left. Or another instruction with same name and arg types exists 669 integer addInstruction(ref NaInst inst, void delegate() ptr = null){ 670 if (inst.code == 0){ 671 // find code 672 foreach (ushort i; 1 .. ushort.max){ 673 if (i ! in _instructions){ 674 inst.code = i; 675 break; 676 } 677 } 678 if (inst.code == 0) 679 return -1; 680 }else if (inst.code in _instructions) 681 return -1; 682 // now make sure no other instruction with same name can be called with these args 683 if (getInstruction(inst.name, inst.arguments) == -1){ 684 _instructions[inst.code] = inst; 685 _instPtrs[inst.code] = ptr; 686 return inst.code; 687 } 688 return -1; 689 } 690 /// Finds the instruction with matching code 691 /// 692 /// Returns: the instruction. 693 /// 694 /// Throws: Exception if doesnt exist 695 NaInst getInstruction(ushort code){ 696 foreach (inst; _instructions){ 697 if (inst.code == code) 698 return inst; 699 } 700 throw new Exception("instruction with code=" ~ code.to!string ~ " does not exist"); 701 } 702 /// Finds an instruction that can be called with arguments with a matching name 703 /// 704 /// Returns: the instruction code for an instruction that can be called, or -1 if doesnt exist 705 integer getInstruction(string name, NaInstArgType[] arguments){ 706 foreach (j, inst; _instructions){ 707 if (inst.name == name && inst.arguments.length == arguments.length){ 708 bool argsMatch = true; 709 foreach (i; 0 .. arguments.length){ 710 if (inst.arguments[i] != arguments[i]){ 711 argsMatch = false; 712 break; 713 } 714 } 715 if (argsMatch) 716 return inst.code; 717 } 718 } 719 return -1; 720 } 721 /// gets pointer for an instruction. **This can be null** 722 /// 723 /// Returns: instruction pointer 724 /// 725 /// Throws: Exception if instruction does not exist 726 void delegate() getInstructionPtr(ushort code){ 727 if (code in _instPtrs) 728 return _instPtrs[code]; 729 throw new Exception("instruction with code=" ~ code.to!string ~ " does not exist"); 730 } 731 /// whether an instruction exists 732 /// Returns: true if an instruction exists 733 bool instructionExists(ushort code){ 734 return (code in _instructions) !is null; 735 } 736 /// ditto 737 bool instructionExists(string name){ 738 foreach (inst; _instructions){ 739 if (inst.name == name) 740 return true; 741 } 742 return false; 743 } 744 } 745 746 /// Reads data from a string (which can be string, char, double, integer, bool) 747 /// 748 /// Addresses are read as integers 749 /// 750 /// Returns: the data in NaInstArg 751 /// 752 /// Throws: Exception if data is invalid 753 public NaData readData(string strData, ref NaInstArgType type){ 754 NaData r; 755 if (strData.length == 0) 756 throw new Exception("cannot read data from empty string"); 757 if (["true", "false"].hasElement(strData)){ 758 r.value!bool = strData == "true"; 759 type = NaInstArgType.Boolean; 760 }else if (strData.isNum(false)){ 761 r.value!integer = strData.to!integer; 762 type = NaInstArgType.Integer; 763 }else if (strData.isNum(true)){ 764 r.value!double = strData.to!double; 765 type = NaInstArgType.Double; 766 }else if (strData.length >= 2 && (strData[0 .. 2] == "0x" || strData[0 .. 2] == "0B")){ 767 type = NaInstArgType.Integer; 768 if (strData.length == 2) 769 r.value!integer = 0; 770 if (strData[0 .. 2] == "0x") 771 r.value!integer = readHexadecimal(strData[2 .. $]); 772 else 773 r.value!integer = readBinary(strData[2 .. $]); 774 }else if (strData[0] == '@'){ 775 type = NaInstArgType.Address; 776 if (strData.length == 1) 777 r.value!NaInstArgAddress = NaInstArgAddress(0); 778 else if (strData[1 .. $].isNum(false)) 779 r.value!NaInstArgAddress = NaInstArgAddress(strData[1 .. $].to!integer); 780 else{ 781 strData = strData[1 .. $]; 782 integer commaIndex = strData.indexOf(','); 783 NaInstArgAddress addr; 784 if (commaIndex == -1 && !strData.isNum(false)){ 785 addr.labelOffset = strData; 786 }else{ 787 addr.labelOffset = strData[0 .. commaIndex].lowercase; 788 if (commaIndex + 1 < strData.length){ 789 strData = strData[commaIndex + 1 .. $]; 790 if (strData.isNum(false)) 791 addr.address = strData.to!integer; 792 else 793 throw new Exception("invalid address"); 794 } 795 } 796 r.value!NaInstArgAddress = addr; 797 } 798 }else if (strData[0] == '\"'){ 799 type = NaInstArgType.String; 800 r.value!string = strReplaceSpecial(strData[1 .. $-1]); 801 }else if (strData[0] == '\''){ 802 type = NaInstArgType.Char; 803 strData = strData.dup; 804 strData = strReplaceSpecial(strData[1 .. $ -1]); 805 if (strData.length > 1) 806 throw new Exception("' ' can only contain 1 character"); 807 if (strData.length < 1) 808 throw new Exception("no character provided in ''"); 809 r.value!char = strData[0]; 810 }else{ 811 // probably a label 812 type = NaInstArgType.Label; 813 r.value!string = strData.lowercase; 814 } 815 return r; 816 } 817 /// 818 unittest{ 819 NaInstArgType type; 820 assert("true".readData(type) == NaData(true)); 821 assert(type == NaInstArgType.Boolean); 822 assert("false".readData(type) == NaData(false)); 823 assert(type == NaInstArgType.Boolean); 824 825 assert("15".readData(type).value!integer == 15); 826 assert(type == NaInstArgType.Integer); 827 assert("0".readData(type).value!integer == 0); 828 assert(type == NaInstArgType.Integer); 829 assert("-1".readData(type).value!integer == -1); 830 assert(type == NaInstArgType.Integer); 831 assert("\"str\\t\"".readData(type).value!string == "str\t"); 832 assert(type == NaInstArgType.String); 833 834 assert("potato".readData(type).value!string == "potato"); 835 assert(type == NaInstArgType.Label); 836 837 assert("@1234".readData(type).value!NaInstArgAddress == NaInstArgAddress(1234)); 838 assert(type == NaInstArgType.Address); 839 assert("@1234,12".readData(type).value!NaInstArgAddress == NaInstArgAddress("1234",12)); 840 assert(type == NaInstArgType.Address); 841 assert("@label".readData(type).value!NaInstArgAddress == NaInstArgAddress("label")); 842 assert(type == NaInstArgType.Address); 843 assert("@label,1234".readData(type).value!NaInstArgAddress == NaInstArgAddress("label",1234)); 844 assert(type == NaInstArgType.Address); 845 } 846 847 /// Reads a hexadecimal number from string 848 /// 849 /// Returns: the number in a uinteger 850 /// 851 /// Throws: Exception in case string is not a hexadecimal number, or too big to store in uinteger, or empty string 852 private uinteger readHexadecimal(string str){ 853 import std.range : iota, array; 854 if (str.length == 0) 855 throw new Exception("cannot read hexadecimal number from empty string"); 856 if (str.length > uinteger.sizeof * 2) // str.length / 2 = numberOfBytes 857 throw new Exception("hexadecimal number is too big to store in uinteger"); 858 static char[16] DIGITS = iota('0', '9'+1).array ~ iota('a', 'f'+1).array; 859 str = str.lowercase; 860 if (!(cast(char[])str).matchElements(DIGITS)) 861 throw new Exception("invalid character in hexadecimal number"); 862 uinteger r; 863 immutable uinteger lastInd = str.length - 1; 864 foreach (i, c; str){ 865 r |= DIGITS.indexOf(c) << 4 * (lastInd-i); 866 } 867 return r; 868 } 869 /// 870 unittest{ 871 assert("FF".readHexadecimal == 0xFF); 872 assert("F0".readHexadecimal == 0xF0); 873 assert("EF".readHexadecimal == 0xEF); 874 assert("A12F".readHexadecimal == 0xA12F); 875 } 876 877 /// Reads a binary number from string 878 /// 879 /// Returns: the number in a uinteger 880 /// 881 /// Throws: Exception in case string is not a binary number, or too big to store in uinteger, or empty string 882 private uinteger readBinary(string str){ 883 if (str.length == 0) 884 throw new Exception("cannot read binary number from empty string"); 885 if (str.length > uinteger.sizeof * 8) 886 throw new Exception("binary number is too big to store in uinteger"); 887 if (!(cast(char[])str).matchElements(['0','1'])) 888 throw new Exception("invalid character in binary number"); 889 uinteger r; 890 immutable uinteger lastInd = str.length-1; 891 foreach (i, c; str){ 892 if (c == '1') 893 r |= 1 << (lastInd - i); 894 } 895 return r; 896 } 897 /// 898 unittest{ 899 assert("01010101".readBinary == 0B01010101); 900 } 901 902 /// reads a string into substrings separated by whitespace. Strings are read as a whole 903 /// 904 /// Returns: substrings 905 /// 906 /// Throws: Exception if string not closed 907 private string[] separateWhitespace(char[] whitespace=[' ','\t'], char comment='#')(string line){ 908 string[] r; 909 for (uinteger i, readFrom; i < line.length; i++){ 910 immutable char c = line[i]; 911 if (c == comment){ 912 if (readFrom < i) 913 r ~= line[readFrom .. i].dup; 914 break; 915 } 916 if (c == '"' || c == '\''){ 917 if (readFrom < i) 918 r ~= line[readFrom .. i].dup; 919 readFrom = i; 920 immutable integer endIndex = line.strEnd(i); 921 if (endIndex < 0) 922 throw new Exception("string not closed"); 923 r ~= line[readFrom .. endIndex+1].dup; 924 readFrom = endIndex+1; 925 i = endIndex; 926 continue; 927 } 928 if (whitespace.hasElement(c)){ 929 if (readFrom < i) 930 r ~= line[readFrom .. i].dup; 931 while (i < line.length && whitespace.hasElement(line[i])) 932 i ++; 933 readFrom = i; 934 i --; // back to whitespace, i++ in for(..;..;) exists 935 continue; 936 } 937 if (i+1 == line.length && readFrom <= i){ 938 r ~= line[readFrom .. $].dup; 939 } 940 } 941 return r; 942 } 943 /// 944 unittest{ 945 assert("potato".separateWhitespace == ["potato"]); 946 assert("potato potato".separateWhitespace == ["potato", "potato"]); 947 assert(" a b \"str\"".separateWhitespace == ["a", "b", "\"str\""]); 948 assert("a b 'c' \"str\"".separateWhitespace == ["a", "b", "'c'", "\"str\""]); 949 assert("\ta \t b\"str\"".separateWhitespace == ["a", "b", "\"str\""]); 950 assert(" a b 'c'\"str\"'c'".separateWhitespace == ["a", "b", "'c'", "\"str\"", "'c'"]); 951 assert("a 'b'#c".separateWhitespace == ["a", "'b'"]); 952 assert("a: a b#c".separateWhitespace == ["a:","a", "b"]); 953 assert("a 'b' #c".separateWhitespace == ["a", "'b'"]); 954 } 955 956 /// ditto 957 private string[][] separateWhitespace(string[] lines){ 958 string[][] r; 959 r.length = lines.length; 960 foreach (i; 0 .. lines.length) 961 r[i] = separateWhitespace(lines[i]); 962 return r; 963 } 964 965 /// Returns: the index where a string ends, -1 if not terminated 966 private integer strEnd(char specialCharBegin='\\')(string s, uinteger startIndex){ 967 uinteger i; 968 immutable char strTerminator = s[startIndex]; 969 for (i = startIndex+1; i < s.length; i ++){ 970 if (s[i] == strTerminator){ 971 return i; 972 } 973 if (s[i] == specialCharBegin){ 974 i ++; 975 continue; 976 } 977 } 978 return -1; 979 } 980 /// 981 unittest{ 982 assert("st\"sdfsdfsd\"0".strEnd(2) == 11); 983 } 984 985 /// Returns: string with special characters replaced with their actual characters (i.e, \t replaced with tab, \n with newline...) 986 private string strReplaceSpecial(char specialCharBegin='\\') 987 (string s, char[char] map = ['t' : '\t', 'n' : '\n','\\':'\\']){ 988 char[] r = []; 989 for (uinteger i = 0; i < s.length; i ++){ 990 if (s[i] == specialCharBegin && i + 1 < s.length && s[i+1] in map){ 991 r ~= map[s[i+1]]; 992 i++; 993 continue; 994 } 995 r ~= s[i]; 996 } 997 return cast(string)r; 998 } 999 /// 1000 unittest{ 1001 assert("newline:\\ntab:\\t".strReplaceSpecial == "newline:\ntab:\t"); 1002 }