Source code for /engineering/autohit-1998/autohit/vm/VM.javaOriginal file VM.java
   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 }