1 /**
2 * .
3 * Copyright � 1999 Erich P G.
4 *
5 */
6
7 package autohit.vm;
8
9 import java.util.Date;
10 import java.util.Hashtable;
11 import java.util.Stack;
12
13 import autohit.vm.VMIScope;
14
15 /**
16 * The abstract base class for virtual machines.
17 *
18 * A derived class will implement the abstract execute()
19 * method to actually run the VM. Also, it would normally use the
20 * pause() and resume() methods to control timing (rather than
21 * overloading and re-implementing them).
22 * <p>
23 * The derived class MAY overload the method prepare() if it has
24 * anything it wants to do before the FIRST instruction (and only the
25 * first) is executed. For instance, it could add environment variables.
26 * <p>
27 * The pause() and resume() methods are not designed to be
28 * called by external threads. If you plan to wrap the
29 * derived vm in a threaded class, you may want to overload or
30 * just not use those methods outside of the vm. Also, these methods
31 * only manage state and timing; it is up the he execute() method
32 * in the derived class to actually stop execution.
33 * <p>
34 * This base class offers the following services:
35 * - Instruction pointer (ip) field<br>
36 * - Variable space allocation (vars). The variables are put into a hashtable.
37 * some convenience methods are provided.<br>
38 * - Scope Stack space and convenience methods.<br>
39 * - Scope stack dirty flag.
40 * <p>
41 * USE <b>THESE</b> SERVICES! Do not make your own ip, for instance! Methods of
42 * this class depend upon it.
43 *
44 * @author Erich P. Gatejen
45 * @version 1.0
46 * <i>Version History</i>
47 * <code>EPG - Initial - 15Jan99</code>
48 *
49 */
50 public abstract class VM {
51
52 // --- FINAL FIELDS ------------------------------------------------------
53
54 /**
55 * Granulatiry for each tick of the VM's clock. It
56 * is used to scale the system time to the vm clock time.
57 *
58 * Currently, it is set to 1. Given the current Java
59 * implementations, this should yield a 1 millisecond
60 * tick. That is, each VM clock tick will take one
61 * millisecond.
62 */
63 public static final int TIME_GRAN = 1;
64
65 /**
66 * State values for the VM.
67 */
68 public static final int STATE_NEW = 0;
69 public static final int STATE_RUNNING = 1;
70 public static final int STATE_PAUSED = 2;
71 public static final int STATE_DONE = 3;
72 public static final int STATE_NO_VM = 4;
73
74 // --- FIELDS ------------------------------------------------------------
75
76 /**
77 * VM start time. System start time for this VM.
78 * This is a raw value that has not been scaled with the
79 * TIME_GRAN value. We don't even want the derived class
80 * to get direct access to this, in case we have to
81 * compensate for a pause.
82 */
83 private long time;
84
85 /**
86 * Used to compensate the time field after a resume().
87 * Since the system clock doesn't stop during a pause,
88 * we will have to change our perceived system time start
89 * to get the ticks for the VM.
90 */
91 private long pauseCompensation;
92
93 /**
94 * VM state.
95 */
96 protected int state;
97
98 /**
99 * Current instruction address/pointer. A pointer into the insrtuction Vector.
100 */
101 public int ip;
102
103 /**
104 * Variable space. Use the convenience methods to access these...
105 */
106 protected Hashtable vars;
107
108 /**
109 * Scope stack.
110 *
111 * Do NOT use scope.pop() or scope.push() yourself! We must maintain the
112 * scope cache dirty flag. However, you can use peek(), empty(), and
113 * search() at your leasure().
114 */
115 protected Stack scope;
116
117 /**
118 * Scope stack cache dirty flag. Will be automatically set when any
119 * scope stack methods are used.
120 */
121 protected boolean scDirty;
122
123 // --- PUBLIC METHODS ----------------------------------------------------
124
125
126 /**
127 * Constructor. This should be called by any derived
128 * class constructors.
129 *
130 * It does NOT set any time/clock data, so if you
131 * overload start(), be sure to do it in your start().
132 * (Oh, and don't do it in the constructor.)
133 */
134 public VM() {
135
136 state = STATE_NEW;
137 ip = 0; // Always start at home. :-)
138 vars = new Hashtable();
139 scope = new Stack();
140
141 scDirty = false;
142 }
143
144 /**
145 * Start the VM. It will set state and timing info, then
146 * call the abstract method execute() to execute the
147 * code.
148 * <p>
149 * Calling this method consecutively will effectively
150 * reset the state and timing info. It is probibly a
151 * REAL BAD IDEA to call this from the execute method.
152 * <p>
153 * It throws any exceptions that are thrown out of execute().
154 *
155 * @throws autohit.vm.VMException
156 */
157 public void start() throws VMException {
158
159 state = STATE_NEW;
160 Date d = new Date();
161 time = d.getTime();
162
163 try {
164 prepare();
165
166 } catch (Exception e) {
167 throw new VMException(VMException.PREPARE_EXCEPTION, e.getMessage());
168 }
169
170 execute();
171 }
172
173 /**
174 * Get VM state. Reports the state of the vm using the
175 * STATE_* values.
176 * <p>
177 * You may call this from another thread, but it isn't
178 * very reliable.
179 *
180 * @return a STATE_* value
181 */
182 public int getState() {
183 return state;
184 }
185
186 /**
187 * Pause execution in the VM. This should NOT be called
188 * by another thread.
189 * <p>
190 * It will only pause if the VM is running.
191 */
192 public void pause() {
193
194 if (state == STATE_RUNNING) {
195 state = STATE_PAUSED;
196 Date d = new Date();
197 pauseCompensation = -(d.getTime() - time);
198 }
199 }
200
201 /**
202 * Resume execution in the VM. This should NOT be called
203 * by another thread.
204 * <p>
205 * It will only resume if the VM is paused.
206 */
207 public void resume() {
208
209 if (state == STATE_PAUSED) {
210 state = STATE_RUNNING;
211 Date d = new Date();
212 time = d.getTime() - pauseCompensation;
213 pauseCompensation = d.getTime() - time;
214 }
215 }
216
217 /**
218 * Number of ticks the VM has been running. It will
219 * be scaled according to the TIME_GRAN field.
220 * <p>
221 * Note that it returns an int rather than a long like
222 * system time usually is. This means that the VM timing.
223 * This technically could cause some overflow problems, but
224 * I doubt a VM would ever run that long.
225 *
226 * @return number of ticks the VM has run.
227 */
228 public int ticks() {
229
230 Date d = new Date();
231
232 long sticks = d.getTime() - time;
233
234 return (int) (sticks / TIME_GRAN);
235
236 // If the compiler has half of a brain, this division
237 // should be optimised out given the current
238 // granularity.
239 }
240
241
242 /**
243 * Set a variable. If the variable doesn't exist, it will create it. Once
244 * created, a variable stays in scope for the rest of execution.
245 *
246 * @param name the variable name.
247 * @param value the variable value given as a string.
248 */
249 public void setVar(String name, String value) {
250
251 vars.put(name, value);
252 }
253
254 /**
255 * Set a variable object. If the variable doesn't exist, it will create it. Once
256 * created, a variable stays in scope for the rest of execution. The variable value is
257 * an object.
258 *
259 * @param name the variable name.
260 * @param value the variable object.
261 */
262 public void setVar(String name, Object value) {
263
264 vars.put(name, value);
265 }
266
267
268 /**
269 * Remove a variable. If the variable is not present, no error
270 * occurs.
271 *
272 * @param name the variable name.
273 */
274 public void removeVar(String name) {
275
276 vars.remove(name);
277 }
278
279 /**
280 * Get a string variable. it will throw a VMException if the variable
281 * has not been set.
282 *
283 * @param name the variable name.
284 * @return the value as a String
285 * @throws VMException
286 * @see VMException
287 */
288 public String getVar(String name) throws VMException {
289
290 Object var = vars.get(name);
291 if (var == null) {
292 throw new VMException( VMException.VARIABLE_NOT_DEFINED, "Variable " + name + " not defined.");
293 }
294
295 return (String)var;
296 }
297
298
299 /**
300 * Get a object variable. it will throw a VMException if the variable
301 * has not been set.
302 *
303 * @param name the variable name.
304 * @return the value as a String
305 * @throws VMException
306 * @see VMException
307 */
308 public Object getVarObject(String name) throws VMException {
309
310 Object var = vars.get(name);
311 if (var == null) {
312 throw new VMException( VMException.VARIABLE_NOT_DEFINED, "Variable " + name + " not defined.");
313 }
314
315 return var;
316 }
317
318 /**
319 * Get an Integer variable. it will throw a VMException if the variable
320 * has not been set or is not a parse-able integer.
321 *
322 * @param name the variable name.
323 * @return the value as an int
324 * @throws VMException
325 * @see VMException
326 */
327 public int getIntegerVar(String name) throws VMException {
328
329 int value;
330 String var = (String)vars.get(name);
331 if (var == null) {
332 throw new VMException( VMException.VARIABLE_NOT_DEFINED, "Variable " + name + " not defined.");
333 }
334
335 try {
336 value = Integer.parseInt(var);
337
338 } catch (Exception e) {
339 throw new VMException( VMException.VARIABLE_TYPE_MISMATCH, "Variable " + name + " type mismatch. Expecting integer, but it is a string.");
340 }
341
342 return value;
343 }
344
345 /**
346 * Variable substitution.
347 * <p>
348 * There won't be any errors if a substitution isn't found.
349 * It will only do one level of substitution. Either call this again
350 * to resolve a variable in the newly substituted text, or just
351 * assume it is plain text.
352 * <p>
353 * It will throw a VMException if the variable isn't set.
354 * <p>
355 * Performing substitution on Object variables will yield "undefined" results.
356 *
357 * @param in text to find substitution.
358 * @return string with substitutions.
359 * @throws VMException
360 * @see VMException
361 */
362
363 public String subVar(String in) throws VMException {
364
365 StringBuffer temp = new StringBuffer();
366 StringBuffer var = temp;
367 String varValue;
368 int rover = 0;
369 int sl = in.length();
370 boolean replacing = false;
371 char cur;
372
373 while (rover < sl) {
374
375 cur = in.charAt(rover);
376 if (cur == VMInstruction.IVToken) {
377
378 if (replacing) {
379
380 // the token is NOT allowed in a variable name...
381 // so go ahead and assume this is a close-out
382 varValue = (String)vars.get(var.toString());
383 if (varValue == null) {
384 throw new VMException( VMException.VARIABLE_NOT_DEFINED, "Variable " + var.toString() + " not defined.");
385 }
386 temp.append(varValue);
387 replacing = false;
388
389 } else {
390
391 // a double token is just escaping the token.
392 if (rover == (sl - 1)) {
393 // Abhorant case. A lone token at the end of
394 // the string.
395 throw new VMException( VMException.VARIABLE_NOT_DEFINED, "Malformed string has a VARIABLE key character as the last character of the string.");
396
397 } else {
398
399 if (in.charAt(rover+1) == VMInstruction.IVToken) {
400 // Just escaped the token
401 temp.append(VMInstruction.IVToken);
402 rover++;
403
404 } else {
405 // OK. a NEW variable replace.
406 var = new StringBuffer();
407 replacing = true;
408 }
409
410 } // end if escaping
411
412 } // end if replacing.
413
414 } else {
415
416 if (replacing) {
417 var.append(cur);
418
419 } else {
420 temp.append(cur);
421 }
422
423 } //end if IVToken
424
425 rover++;
426
427 } // end while
428
429 // Bad thing if a variable was not closed.
430 if (replacing) {
431 throw new VMException( VMException.VARIABLE_NOT_DEFINED, "Malformed string gives an unbounded variable name [" + in + "]");
432 }
433
434 return temp.toString();
435 }
436
437
438 /**
439 * Push an object onto the scope stack.
440 *
441 * @param i the object
442 */
443 public void pushScope(Object o) {
444
445 scDirty = true;
446
447 scope.push(o);
448 }
449
450
451 /**
452 * Pop an object off the stack. USE THIS instead of scope.pop()!!!
453 * Have to dirty the cache flag...
454 *
455 * It'll throw any exception it encounters--most likely a EmptyStackException.
456 * @return an object reference
457 */
458 public Object popScope() throws Exception{
459
460 scDirty = true;
461
462 return scope.pop();
463 }
464
465 /**
466 * Discard scope frame. This will remove all items on the scope to and
467 * including the top-most recent VMIScope object.
468 * <p>
469 * If it encounters any variable references, the variable will
470 * discarded.
471 * <p>
472 * It will pop the whole damned stack if it doesn't find one...
473 *
474 */
475 public void discardScopeFrame() {
476
477 scDirty = true;
478
479 Object item;
480
481 try {
482
483 while( !(scope.peek() instanceof VMIScope) ) { // perhaps just look at the token instead? faster?
484 item = scope.pop();
485 if (item instanceof String) {
486 // Remove it. Don;'t care if it isnt actually there
487 vars.remove((String)item);
488 }
489 }
490 scope.pop(); // get the SCOPE too.
491
492 } catch (Exception e) {
493 // looks like we emptied the whole stack.
494 }
495 }
496
497 /**
498 * Absract method for VM execution. The derived class
499 * must implement the actual execution. This method will
500 * be automatically called by start(). Therefore, you
501 * probibly should not call start() from within this
502 * method.
503 * <p>
504 * The implimentation of this method should only execute
505 * ONE INSTRUCTION. Successive calls would then execute
506 * the entire program. If you do not impliment it this way,
507 * you are likely to ghost the vm's.
508 * <p>
509 * NOTE! An implementing method MUST throw a
510 * VMException(VMException.DONE) when it reaches the
511 * end of execution.
512 * <p>
513 * If the derived-class VM encounters an instruction that
514 * it does now support, it should throw a
515 * VMException.INVALID_INSTRUCTION.
516 *
517 * @see autohit.vm.VMException
518 */
519 public abstract void execute() throws VMException;
520
521 /**
522 * Prepare for execution of the first instruction. The derived
523 * class may overload this if it has any stuff it wants to do
524 * before execute() is called the first time.
525 *
526 * @throws Any exceptions it encounters.
527 */
528 public void prepare() throws Exception {
529
530 // The base class doesn't wanna do anything...
531 }
532
533
534 // --- PRIVATE METHODS ---------------------------------------------------
535
536 }
|