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 }