1 module navm.navm;
2 
3 
4 import std.conv : to;
5 
6 import utils.ds;
7 import utils.misc;
8 
9 public import navm.bytecode;
10 
11 /// Stack, with no fixed data type for elements.
12 /// 
13 /// Right now its not used anywhere, but it migth be useful.
14 public class NaStack{
15 private:
16 	ubyte[] _stack;
17 	ubyte* _ptr;
18 	ubyte* _lastPtr;
19 	bool _errored; // if error occurred since last checked
20 public:
21 	/// constructor
22 	/// size is the size in bytes, default is 64 KiB
23 	this(uinteger size = 1 << 16){
24 		_stack.length = size;
25 		_lastPtr = _stack.ptr + size;
26 		_ptr = _stack.ptr;
27 	}
28 	~this(){
29 		.destroy(_stack);
30 	}
31 	/// if an error has occured, since this was last called
32 	@property bool errored(){
33 		immutable bool r = _errored;
34 		_errored = false;
35 		return r;
36 	}
37 	/// seek index (number of bytes used on stack)
38 	@property uinteger seek(){
39 		return _ptr - _stack.ptr;
40 	}
41 	/// empty stack, sets all bytes to zero, resets seek
42 	void clear(){
43 		_stack[] = 0;
44 		_ptr = _stack.ptr;
45 		_errored = false;
46 	}
47 	/// pushes to stack
48 	void push(T)(T element){
49 		if (_ptr + T.sizeof > _lastPtr){
50 			_errored = true;
51 			return;
52 		}
53 		_ptr[0 .. T.sizeof] = (cast(ubyte*)&element)[0 .. T.sizeof];
54 		_ptr += T.sizeof;
55 	}
56 	/// pushes multiple elements to stack. last in array is first pushed
57 	/// 
58 	/// Either all are pushed, or none if there isn't enough space
59 	void pushArray(T)(T[] elements){
60 		immutable uinteger lenBytes = T.sizeof * elements.length;
61 		if (_ptr + lenBytes > _lastPtr){
62 			_errored = true;
63 			return;
64 		}
65 		_ptr[0 .. lenBytes] = (cast(ubyte*)elements.ptr)[0 .. lenBytes];
66 		_ptr += lenBytes;
67 	}
68 	/// pops from stack
69 	/// 
70 	/// Sets `val` to popped value
71 	void pop(T)(ref T val){
72 		if (_ptr < _stack.ptr + T.sizeof){
73 			_errored = true;
74 			return;
75 		}
76 		_ptr -= T.sizeof;
77 		val = *(cast(T*)_ptr);
78 	}
79 	/// pops multiple from stack. first popped is last in array
80 	/// 
81 	/// Writes popped values to `vals`. If there arent enough elements on stack, will not pop anything
82 	void popArray(T)(ref T[] vals){
83 		immutable uinteger lenBytes = T.sizeof * vals.length;
84 		if (_ptr < _stack.ptr + lenBytes){
85 			_errored = true;
86 			return;
87 		}
88 		_ptr -= lenBytes;
89 		vals[0 .. vals.length] = (cast(T*)_ptr)[0 .. vals.length];
90 	}
91 }
92 /// 
93 unittest{
94 	NaStack s = new NaStack();
95 	s.push!integer(integer.max);
96 	s.push!ubyte(255);
97 	s.push!byte(127);
98 	byte b;
99 	b = 0;
100 	ubyte ub;
101 	ub = 0;
102 	integer[] iA = [0];
103 	assert(s.errored == false);
104 	s.pop(b);
105 	s.pop(ub);
106 	s.pop(iA[0]);
107 	assert(b == 127);
108 	assert(ub == 255);
109 	assert(iA[0] == integer.max);
110 	iA = [integer.max, integer.max >> 1, 0, 1025];
111 	s.pushArray(iA);
112 	iA[] = 0;
113 	s.popArray(iA);
114 	assert(iA == [integer.max, integer.max >> 1, 0, 1025]);
115 	s.push(cast(integer)integer.max);
116 	s.push(cast(integer)integer.max >> 1);
117 	s.push(cast(integer)0);
118 	s.push(cast(integer)1025);
119 	iA[] = 0;
120 	s.popArray(iA);
121 	assert(iA == [integer.max, integer.max >> 1, 0, 1025]);
122 	assert(s.errored == false);
123 	.destroy(s);
124 }
125 
126 /// NaVM abstract class
127 public abstract class NaVM{
128 protected:
129 	void delegate()[] _instructions; /// the instruction pointers
130 	ubyte[] _args; /// stores arguments
131 	uinteger _instIndex; /// index of next instruction
132 	uinteger _argIndex; /// index next argument
133 	uinteger[] _labelInstIndexes; /// instruction indexes for labels
134 	uinteger[] _labelArgIndexes; /// argument indexes for labels
135 	string[] _labelNames; /// label names
136 
137 	NaInstTable _instTable; /// instruction table
138 
139 	/// Gets an argument. **Do not use this when argument is array (string)**
140 	T _readArg(T)(){
141 		immutable T r = *(cast(T*)(_args.ptr + _argIndex*(_argIndex + T.sizeof <= _args.length)));
142 		_argIndex += T.sizeof;
143 		return r;
144 	}
145 	/// ditto
146 	T _readArg(T)(uinteger argAddr){
147 		return *(cast(T*)(_args.ptr + argAddr*(argAddr + T.sizeof <= _args.length)));
148 	}
149 	/// Reads an array from arguments. Will try to read enough bytes to fill `array`
150 	void _readArgArray(T)(T[] array){
151 		immutable uinteger lenBytes = T.sizeof * array.length;
152 		immutable uinteger altLenBytes = _args.length - _argIndex;
153 		if (lenBytes > altLenBytes){
154 			(cast(ubyte*)array.ptr)[0 .. altLenBytes] = _args[_argIndex .. $];
155 			_argIndex = _args.length;
156 			return;
157 		}
158 		(cast(ubyte*)array.ptr)[0 .. lenBytes] = _args[_argIndex .. _argIndex + lenBytes];
159 		_argIndex += lenBytes;
160 	}
161 	/// Changes value of an argument. **Do not use this when argument is array (string)**
162 	/// 
163 	/// Returns: true if done, false if argument address is out of bounds
164 	bool _writeArg(T)(uinteger argAddr, T val){
165 		if (argAddr + T.sizeof > _args.length)
166 			return false;
167 		*cast(T*)(_args.ptr + argAddr) = val;
168 		return true;
169 	}
170 public:
171 	/// constructor
172 	this(){
173 		_instTable = new NaInstTable();
174 	}
175 	~this(){
176 		.destroy(_instTable);
177 	}
178 	/// instruction table.  
179 	/// 
180 	/// **this will be destroyed when this class is destroyed**
181 	@property NaInstTable instTable(){
182 		return _instTable;
183 	}
184 	/// label names, at corresponding label index
185 	@property string[] labelNames(){
186 		return _labelNames;
187 	}
188 	/// loads bytecode
189 	/// 
190 	/// Overriding:  
191 	/// this function must initialize `_instructions`, `_args`, `_argIndex`,
192 	/// `_instIndex`, `_labelInstIndexes`, `_labelArgsIndexes`, and `_labelNames`.  
193 	/// 
194 	/// Returns: [] on success, or errors in case of any
195 	string[] loadBytecode(NaBytecode code){
196 		if (!code.verify)
197 			return ["bytecode.verify returned false"];
198 		_instIndex = 0;
199 		_argIndex = 0;
200 		string[] errors;
201 		_instructions = code.instPtrs;
202 		foreach (i, inst; _instructions){
203 			if (inst is null){
204 				try{
205 					NaInst instData = _instTable.getInstruction(code.instCodes[i]);
206 					errors ~= "invalid pointer for instruction `"~instData.name~'`';
207 				}catch (Exception e){
208 					.destroy(e);
209 					errors ~= "invalid instruction code "~code.instCodes[i].to!string;
210 				}
211 			}
212 		}
213 		// now labels
214 		_labelNames = code.labelNames;
215 		_labelInstIndexes.length = _labelNames.length;
216 		_labelArgIndexes.length = _labelNames.length;
217 		foreach (i, indexes; code.labelIndexes){
218 			_labelInstIndexes[i] = indexes[0];
219 			_labelArgIndexes[i] = indexes[1];
220 		}
221 		// append arguments to _args
222 		foreach (i, ref arg; code.instArgs){
223 			immutable NaInstArgType type = code.instArgTypes[i];
224 			if (type == NaInstArgType.String){
225 				ByteUnion!integer sizeStore;
226 				string str = arg.value!string;
227 				sizeStore.data = str.length;
228 				_args ~= sizeStore.array ~ cast(ubyte[])str;
229 			}else if (type == NaInstArgType.Boolean || type == NaInstArgType.Char){
230 				_args ~= arg.value!ubyte;
231 			}else if (type == NaInstArgType.Double){
232 				ByteUnion!double valStore;
233 				valStore.data = arg.value!double;
234 				_args ~= valStore.array;
235 			}else if (type == NaInstArgType.Integer || type == NaInstArgType.Label ||
236 				type == NaInstArgType.Address){
237 				ByteUnion!integer valStore;
238 				valStore.data = arg.value!integer;
239 				_args ~= valStore.array;
240 			}
241 		}
242 		return errors;
243 	}
244 	/// starts execution from a label. Will do nothing if label invalid or doesnt exist
245 	void execute(string labelName){
246 		integer index = _labelNames.indexOf(labelName);
247 		if (index > -1)
248 			execute(index);
249 	}
250 	/// ditto
251 	void execute(uinteger labelIndex){
252 		if (labelIndex >= _labelInstIndexes.length)
253 			return;
254 		_argIndex = _labelArgIndexes[labelIndex];
255 		_instIndex = _labelInstIndexes[labelIndex];
256 		while (_instIndex < _instructions.length)
257 			_instructions[_instIndex++]();
258 	}
259 }