Source code for /engineering/autohit-2003/src/autohit/vm/SimVM.javaOriginal file SimVM.java
   1 /**
   2  * AUTOHIT 2003
   3  * Copyright Erich P Gatejen (c) 1989,1997,2003,2004
   4  * 
   5  * This program is free software; you can redistribute it and/or modify 
   6  * it under the terms of the GNU General Public License as published by 
   7  * the Free Software Foundation; either version 2 of the License, or (at
   8  * your option) any later version.
   9  * This program is distributed in the hope that it will be useful, but
  10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  12  * more details.
  13  * 
  14  * You should have received a copy of the GNU General Public License along
  15  * with this program; if not, write to the Free Software Foundation, Inc.,
  16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17  *
  18  * Additional license information can be found in the documentation.
  19  * @author Erich P Gatejen
  20  */
  21 package autohit.vm;
  22 
  23 import autohit.call.Call;
  24 import autohit.call.CallException;
  25 import autohit.call.Call_METHOD;
  26 import autohit.common.AutohitErrorCodes;
  27 import autohit.common.Constants;
  28 import autohit.creator.SimLanguage;
  29 import autohit.vm.i.VMIAssert;
  30 import autohit.vm.i.VMICall;
  31 import autohit.vm.i.VMIClear;
  32 import autohit.vm.i.VMIEval;
  33 import autohit.vm.i.VMIExec;
  34 import autohit.vm.i.VMIFetch;
  35 import autohit.vm.i.VMIGoto;
  36 import autohit.vm.i.VMIIf;
  37 import autohit.vm.i.VMIJump;
  38 import autohit.vm.i.VMILoad;
  39 import autohit.vm.i.VMIMath;
  40 import autohit.vm.i.VMIMerge;
  41 import autohit.vm.i.VMIMethod;
  42 import autohit.vm.i.VMINew;
  43 import autohit.vm.i.VMINop;
  44 import autohit.vm.i.VMIRScope;
  45 import autohit.vm.i.VMIReduce;
  46 import autohit.vm.i.VMIScope;
  47 import autohit.vm.i.VMIStore;
  48 import autohit.vm.i.VMISubr;
  49 import autohit.vm.i.VMInstruction;
  50 import autohit.vm.process.StringProcessors;
  51 
  52 /**
  53  * A VM for a Sim.
  54  * <p>
  55  * A VMNOp marks the bottom of the scope stack.  If we ever pop a nop, there
  56  * is nothing left to do.
  57  * <p>
  58  * Subroutine stack frames consist of the IP pointer (to calling instruction) in Integer form and the calling 
  59  * VMExecutable.  When a the IP goes past the last instruction, the SimVM wil
  60  * check the stack for a VMExecutable.  If it sees one, it will assume it is 
  61  * the return from a subroutine.  The LEFT is left alone, since this is how the 
  62  * subroutine passes a return value.  If the subroutine specified a return, but it
  63  * isn't set, the VM will log an error and use a blank return value.  The subroutine depth (subDepth)
  64  * will be incremented and decremented as we enter and leave routines.  This helps up
  65  * keep an exception from unravelling the entire VM.
  66  * <p>
  67  * Currently only the following MATH operations are implemented
  68  * <pre>
  69  * +	= plus
  70  * - 	= minus
  71  * /    = division
  72  * *    = multiply
  73  * </pre>
  74  * <p>
  75  * ERRORS ----------
  76  * 
  77  * <p>
  78  * This VM expects the following environment variables to be set
  79  * before the execute() method is called.  If they aren't set, the defaults
  80  * will be used.
  81  * <p>
  82  *
  83  * @see autohit.vm
  84  *
  85  * @author Erich P. Gatejen
  86  * @version 1.0
  87  * <i>Version History</i>
  88  * <code>EPG - Rewrite - 8May03<br>
  89  * EPG - Add goto - 16Jul03<br>
  90  * EPG - Add assert - 5Aug03<br>
  91  * EPG - Add module shortcut - 9Aug03</code> 
  92  * 
  93  */
  94 public class SimVM extends VM {
  95 
  96 	/**
  97 	 *  The current executable.
  98 	 *
  99 	 *  @see autohit.vm.VMExecutable
 100 	 */
 101 	private VMExecutable mySim;
 102 
 103 	/**
 104 	 *  Subroutine depth.
 105 	 */
 106 	private int subDepth;
 107 
 108 	/**
 109 	 *  Last instruction.  Used to detect runawya programs.
 110 	 */
 111 	private int lastIP;
 112 
 113 	/**
 114 	 *  Scratch object.  Put it here because I have plans for the future
 115 	 */
 116 	private Object scratch;
 117 
 118 	/**
 119 	 *  Current isntruction
 120 	 */
 121 	private VMInstruction ci;
 122 
 123 	/**
 124 	 *  Default Constructor.  Don't do anything!
 125 	 */
 126 	public SimVM() {
 127 		super();
 128 	}
 129 
 130 	/**
 131 	 *  Complete construction.  This will be (should be) called right after a VM
 132 	 *  object is constructed.  This is where you set the target program name.
 133 	 */
 134 	public void construct() {
 135 		right = Constants.EMPTY_LEFT;
 136 		left = Constants.EMPTY_LEFT;
 137 		subDepth = 0;
 138 	}
 139 
 140 	/**
 141 	 * Destroy.  This will be called when the VM is
 142 	 * finalizing.
 143 	 */
 144 	public void destruct() throws VMException {
 145 		// dont do anything
 146 	}
 147 
 148 	/**
 149 	 *  Prepare for execution of the first instruction.  We need to 
 150 	 *  add environment variables.  DO NOT call this method directly.
 151 	 *
 152 	 *  @throws Any exceptions it encounters.
 153 	 */
 154 	public void prepare() throws Exception {
 155 
 156 		// Toss a NOP on the stack.
 157 		core.push(new VMINop());
 158 
 159 		// Load the program
 160 		mySim = loader.load(rootProgram);
 161 
 162 		// last instruction set to soemthing impossible
 163 		lastIP = -99999;
 164 	}
 165 
 166 	/**
 167 	 *  Implements the inherited abstract method execute(). Call this to execute
 168 	 *  a single instruction,  The first call will be automatic after the inherited
 169 	 *  start() method is called.  From there, the owning Object/Thread should 
 170 	 *  call this method for each successive instruction to execute.
 171 	 *  <p>
 172 	 *  This method will throw a VMException(VMException.DONE) if there are
 173 	 *  no more instructions that can be executed.  (The ip is past the
 174 	 *  end of the exec Vector). 
 175 	 *  <p>
 176 	 *  @throws VMException
 177 	 *  @see autohit.vm.VMException
 178 	 */
 179 	public void execute() throws VMException {
 180 
 181 		ci = null;
 182 
 183 		// Have we completed this routine?
 184 		if (ip >= mySim.core.size()) {
 185 
 186 			lastIP = -99999; // something impossible
 187 
 188 			// Pop until we get to something interesting
 189 			// Intermeadiate crap is prolly old stack-frame junk
 190 			while (true) {
 191 				try {
 192 					scratch = core.pop();
 193 				} catch (Exception e) {
 194 					scratch = null;
 195 				}
 196 				if (scratch == null) {
 197 					throw new VMException(
 198 						"Instruction pointer out of bounds.  Stack drained.  All hell has broken loose.",
 199 						VMException.CODE_VM_PANIC);
 200 				}
 201 
 202 				if (scratch instanceof String) {
 203 					// local variable.  Remove it.
 204 					core.remove((String) scratch);
 205 				}
 206 
 207 				if (scratch instanceof VMExecutable) {
 208 					// leaving this subroutine.  change context and cycle
 209 					try {
 210 						exit_subr();
 211 						mySim = (VMExecutable) scratch;
 212 						ip++;
 213 					} catch (VMException e) {
 214 						throw e;
 215 					}
 216 					return;
 217 				}
 218 
 219 				if (scratch instanceof VMINop) {
 220 					// found that NOP at the bottom of the stack.  Must be done.
 221 					throw new VMException(VMException.CODE_VM_DONE);
 222 				}
 223 
 224 			} // end while
 225 		} // end if
 226 
 227 		try {
 228 
 229 			// check for a runaway program that just keeps executing the
 230 			// same dead command.  This is what happens when a GOTO
 231 			// points at itself.  :^)
 232 			if (ip == lastIP) {
 233 				ip++; // kick it
 234 				myLog.error(
 235 					"VMSim:"
 236 						+ mySim.name
 237 						+ " Detected a runaway program that is looping on the same instruction.   IP kicked by one, but the VM is probibly unstable.",
 238 					AutohitErrorCodes.CODE_VM_INSTRUCTION_ABORT);
 239 				return;
 240 			} else {
 241 				lastIP = ip;
 242 			}
 243 
 244 			ci = (VMInstruction) mySim.core.get(ip);
 245 			// NOTES:  Each instruction is responsible for advancing the
 246 			// ip.
 247 			//DEBUG
 248 			//myLog.debug("SIMVM: get token at ip=" + ip + " is=" + ci.instruction);
 249 
 250 			switch (ci.instruction) {
 251 
 252 				case VMInstruction.CALL :
 253 					// i.call(target)	  : call TARGET, target put result in LEFT, store LEFT in result.
 254 					handleCall((VMICall) ci);
 255 					ip++;
 256 					break;
 257 
 258 				case VMInstruction.METHOD :
 259 					// i.method(n,m)	  : call MATHOD, target put result in LEFT, store LEFT in result.
 260 					handleMethod((VMIMethod) ci);
 261 					ip++;
 262 					break;
 263 
 264 				case VMInstruction.CLEAR :
 265 					// i.clear(buffer) : clear a buffer
 266 					if (core.exists(((VMIClear) ci).t)) {
 267 						core.replace(((VMIClear) ci).t, new StringBuffer());
 268 					} else {
 269 						// It's a brand new buffer
 270 						core.store(((VMIClear) ci).t, new StringBuffer());
 271 					}
 272 					ip++;
 273 					break;
 274 
 275 				case VMInstruction.EVAL :
 276 					// * i.eval(literal)	  : evaluate and store in LEFT(literal)
 277 					handleEval((VMIEval) ci);
 278 					ip++;
 279 					break;
 280 
 281 				case VMInstruction.EXEC :
 282 					// i.exec(class)	  : exec TARGET, target put result in LEFT, store LEFT in result.
 283 					handleExec((VMIExec) ci);
 284 					ip++;
 285 					break;
 286 
 287 				case VMInstruction.FAULT :
 288 					// i.fault  	  : push the IP out of bounds.  this will bust us
 289 					// out of this routine.
 290 					ip = mySim.core.size() + 1;
 291 					break;
 292 
 293 				case VMInstruction.FETCH :
 294 					// i.fetch(variable) : load LEFT from storage specified
 295 					// may not be a buffer
 296 					// TODO SimVM currently only supports String and StringBuffer for passbyreference
 297 					if (core.exists(((VMIFetch) ci).v)) {
 298 						scratch = core.fetch(((VMIFetch) ci).v);
 299 						if ((scratch instanceof String)
 300 							|| (scratch instanceof StringBuffer)) {
 301 							left = scratch;
 302 
 303 						} else {
 304 							//	Blech!  Something bad in this object
 305 							throw new VMException(
 306 								"ERROR in FETCH: Fetched object is not a String or StringBuffer.  This may mean bad things later.  name="
 307 									+ ((VMIFetch) ci).v,
 308 								AutohitErrorCodes.CODE_VM_INSTRUCTION_WARNING);
 309 						}
 310 
 311 					} else {
 312 						// The variable doesn't exist.  Forgive it if the next command is an assert
 313 						// TODO Terrible hack to look ahead for asserts on fetch fail
 314 						boolean faultme = true;
 315 						try {
 316 							if (mySim.core.get(ip + 1) instanceof VMIAssert) {
 317 								faultme = false;
 318 								left = null;
 319 							}
 320 						} catch (Exception ecccc) {
 321 						}
 322 						// FAULT.
 323 						if (faultme)
 324 							throw new VMException(
 325 								"FETCH failed: Variable does not exist.  name="
 326 									+ ((VMIFetch) ci).v,
 327 								AutohitErrorCodes
 328 									.CODE_VM_VARIABLE_NOT_DEFINED_FAULT);
 329 					}
 330 					ip++;
 331 					break;
 332 
 333 				case VMInstruction.IF :
 334 					handleIf((VMIIf) ci);
 335 					break;
 336 
 337 				case VMInstruction.ASSERT :
 338 					handleAssert((VMIAssert) ci);
 339 					break;
 340 
 341 				case VMInstruction.JUMP :
 342 					// literal jump
 343 					ip = ((VMIJump) ci).t;
 344 					break;
 345 
 346 				case VMInstruction.GOTO :
 347 					// literal jump
 348 					handleGoto((VMIGoto) ci);
 349 					break;
 350 
 351 				case VMInstruction.LOAD :
 352 					// i.load(literal)   : load literal into LEFT
 353 					left = ((VMILoad) ci).l;
 354 					ip++;
 355 					break;
 356 
 357 				case VMInstruction.MATH :
 358 					// i.math(oper)  	  : execute operation from RIGHT(literal) to LEFT(literal)
 359 					handleMath((VMIMath) ci);
 360 					ip++;
 361 					break;
 362 
 363 				case VMInstruction.MERGE :
 364 					// i.merge(buffer)   : merge LEFT(literal) with named buffer
 365 					handleMerge((VMIMerge) ci);
 366 					ip++;
 367 					break;
 368 
 369 				case VMInstruction.NEW :
 370 					// i.new(variable)   : push LEFT(literal) on stack and mirror to storage
 371 					core.store(((VMINew) ci).v, left);
 372 					ip++;
 373 					break;
 374 
 375 				case VMInstruction.NOP :
 376 					// Just burn the cycle
 377 					ip++;
 378 					break;
 379 
 380 				case VMInstruction.REDUCE :
 381 					// i.reduce(buffer)  : reduce a buffer and put in LEFT(literal)
 382 					handleReduce((VMIReduce) ci);
 383 					ip++;
 384 					break;
 385 
 386 				case VMInstruction.RIGHT :
 387 					// move LEFT(literal) to RIGHT(literal)
 388 					right = left;
 389 					ip++;
 390 					break;
 391 
 392 				case VMInstruction.RSCOPE :
 393 					// pop the stack to the next i.scope.  remove all encounted vars
 394 					core.discardScopeFrame();
 395 					ip++;
 396 					break;
 397 
 398 				case VMInstruction.SCOPE :
 399 					// push to stack as a marker
 400 					core.markScope();
 401 					ip++;
 402 					break;
 403 
 404 				case VMInstruction.STORE :
 405 					// i.store(variable) : update in scope variable.  if does not exist, do a i.new
 406 					if (core.exists(((VMIStore) ci).v)) {
 407 						core.replace(((VMIStore) ci).v, left);
 408 					} else {
 409 						core.store(((VMIStore) ci).v, left);
 410 					}
 411 					ip++;
 412 					break;
 413 
 414 				case VMInstruction.SUBR :
 415 					// 	i.subr(target)	  : fork to ROUTINE, store LEFT in result.
 416 					entry_subr((VMISubr) ci);
 417 					break;
 418 
 419 				case VMInstruction.MASK :
 420 				default :
 421 					throw new VMException(
 422 						"Unsupported instruction encounted by autohit.SimVM.  nToken=["
 423 							+ ci.instruction
 424 							+ "]",
 425 						AutohitErrorCodes.CODE_VM_INVALID_INSTRUCTION_FAULT);
 426 			}
 427 
 428 		} catch (VMException e) {
 429 
 430 			// Act based on the level
 431 			if (VMException.isFault(e.numeric)
 432 				|| VMException.isPanic(e.numeric)) {
 433 
 434 				faults++;
 435 
 436 				// BUST THIS ROUTINE
 437 				if (subDepth > 0) {
 438 					// we are in a subroutine.  Bomb out of it.  Find the stack bottom or 
 439 					// the bottom of the subroutine stackframe
 440 					try {
 441 						scratch = core.peek();
 442 						while ((scratch != null)
 443 							&& (!(scratch instanceof VMINop))
 444 							&& (!(scratch instanceof VMExecutable))) {
 445 							core.pop();
 446 							scratch = core.peek();
 447 						}
 448 					} catch (Exception ex) {
 449 						// don't care - end of line
 450 					}
 451 					ip = mySim.core.size() + 9999;
 452 					// make sure it is out of bounds
 453 
 454 					myLog.error(
 455 						"VMSim:Routine "
 456 							+ mySim.name
 457 							+ " FAULT at ip="
 458 							+ ip
 459 							+ ".  Message="
 460 							+ e.getMessage(),
 461 						e.numeric);
 462 					faults++;
 463 
 464 				} else {
 465 					// top level - this is the root routine - let the VM unravel
 466 					myLog.error(
 467 						"VMSim:Routine:"
 468 							+ mySim.name
 469 							+ " TERMINAL FAULT at ip="
 470 							+ ip
 471 							+ ".  Message="
 472 							+ e.getMessage(),
 473 						e.numeric);
 474 					dumpei(ci);
 475 					faults++;
 476 					throw e;
 477 				}
 478 
 479 			} else if (VMException.isError(e.numeric)) {
 480 				myLog.error(
 481 					"VMSim:Routine:"
 482 						+ mySim.name
 483 						+ " ERROR at ip="
 484 						+ ip
 485 						+ ".  Message="
 486 						+ e.getMessage(),
 487 					e.numeric);
 488 				errors++;
 489 				if (myLog.debugState())
 490 					dumpei(ci);
 491 
 492 				// make sure the IP advances!
 493 				ip++;
 494 
 495 			} else {
 496 				// Don't consider anything else an error, but log it with that priority
 497 				myLog.error(
 498 					"VMSim:Routine:"
 499 						+ mySim.name
 500 						+ " INFO.  Message="
 501 						+ e.getMessage(),
 502 					e.numeric);
 503 
 504 				// make sure the IP advances!
 505 				ip++;
 506 			}
 507 
 508 		} catch (Exception e) {
 509 			throw new VMException(
 510 				"Unrecoverable Fault:  exception=[" + e.getMessage(),
 511 				AutohitErrorCodes.CODE_VM_PANIC,
 512 				e);
 513 		}
 514 	}
 515 
 516 	/* 
 517 	 * Handle CALL instruction
 518 	 * i.call(target)	  : call TARGET, target put result in LEFT, store LEFT in result.
 519 	 */
 520 	private void handleCall(VMICall instr) throws Exception {
 521 
 522 		try {
 523 
 524 			Call c = loader.get(instr.t, core, myLog);
 525 			left = c.call();
 526 
 527 		} catch (CallException e) {
 528 			if (myLog.debugState())
 529 				myLog.error(
 530 					"CALL Subsystem error.  CALL implementation threw an exception.  Unable to complete CALL. error="
 531 						+ e.getMessage(),
 532 					e.numeric);
 533 			throw e;
 534 
 535 		} catch (VMException e) {
 536 			throw e;
 537 		} catch (Exception e) {
 538 			throw new VMException(
 539 				"CALL Subsystem fault.  Faulted out of a CALL. error="
 540 					+ e.getMessage(),
 541 				VMException.CODE_VM_CALL_FAULT,
 542 				e);
 543 		}
 544 	}
 545 
 546 	/* 
 547 	 * Handle METHOD instruction.  Basically, this is a cheater for
 548 	 * using CALL_METHOD.
 549 	 * i.method()	  : call Module/method, target put result in LEFT, store LEFT in result.
 550 	 */
 551 	private void handleMethod(VMIMethod instr) throws Exception {
 552 
 553 		try {
 554 
 555 			// We know it is a method
 556 			Call_METHOD c = (Call_METHOD) loader.get("METHOD", core, myLog);
 557 			left = c.do_call((String) left, instr.m);
 558 
 559 		} catch (CallException e) {
 560 			if (myLog.debugState())
 561 				myLog.error(
 562 					"METHOD Subsystem error.  METHOD implementation threw an exception.  Unable to complete CALL. error="
 563 						+ e.getMessage(),
 564 					e.numeric);
 565 			throw e;
 566 
 567 		} catch (VMException e) {
 568 			throw e;
 569 		} catch (Exception e) {
 570 			throw new VMException(
 571 				"METHOD Subsystem fault.  Faulted out of a METHOD. error="
 572 					+ e.getMessage(),
 573 				VMException.CODE_VM_CALL_FAULT,
 574 				e);
 575 		}
 576 	}
 577 
 578 	/* 
 579 	 * Handle EXEC instruction
 580 	 * i.exec(class)	  : exec TARGET, target put result in LEFT, store LEFT in result.
 581 	 */
 582 	private void handleExec(VMIExec instr) throws VMException {
 583 
 584 		try {
 585 
 586 			// TODO implement exec
 587 
 588 		} catch (Exception e) {
 589 			throw new VMException(
 590 				" Execution fork Subsystem fault.  Unable to complete EXEC. error="
 591 					+ e.getMessage(),
 592 				AutohitErrorCodes.CODE_VM_EXEC_FAULT,
 593 				e);
 594 		}
 595 	}
 596 
 597 	/* 
 598 	 * Handle GOTO instruction
 599 	 * i.goto(target) : scope sensitive jump.  includes a nasty ass hack to bust any scope frame.
 600 	 */
 601 	private void handleGoto(VMIGoto instr) throws VMException {
 602 
 603 		// decide which way we jump
 604 		int delta = instr.t - ip;
 605 		if (delta == 0) {
 606 			// points at itself.  this is BAD
 607 			throw new VMException(
 608 				"Circular GOTO detected.  This subr will deadlock.  Forcing it to bust out.",
 609 				VMException.CODE_VM_INTENTIONAL_FAULT);
 610 
 611 		} else if (delta == 1) {
 612 			// points at the next instruction.  silly but valid.
 613 			ip++;
 614 
 615 		} else if (delta < 0) {
 616 
 617 			// jumping UP.  Look for any SCOPES and bust their frames
 618 			Object tci;
 619 			int drscopes = 0;
 620 			int dscopes = 0;
 621 			int dbalance = 0;
 622 			for (int i = ip; i > instr.t; i--) {
 623 				tci = mySim.core.get(i);
 624 				if (tci instanceof VMIScope) {
 625 					dscopes++;
 626 				} else if (tci instanceof VMIRScope) {
 627 					drscopes++;
 628 				}
 629 			}
 630 			dbalance = dscopes - drscopes;
 631 			while (dbalance > 0) {
 632 				core.discardScopeFrame();
 633 				dbalance--;
 634 			}
 635 
 636 			ip = instr.t;
 637 
 638 		} else {
 639 
 640 			// jumping DOWN.  Look for any RSCOPES and bust their frames	
 641 			Object tci;
 642 			int drscopes = 0;
 643 			int dscopes = 0;
 644 			int dbalance = 0;
 645 			for (int i = ip; i < instr.t; i++) {
 646 				tci = mySim.core.get(i);
 647 				if (tci instanceof VMIScope) {
 648 					dscopes++;
 649 				} else if (tci instanceof VMIRScope) {
 650 					drscopes++;
 651 				}
 652 			}
 653 			dbalance = drscopes - dscopes;
 654 			while (dbalance > 0) {
 655 				core.discardScopeFrame();
 656 				dbalance--;
 657 			}
 658 
 659 			ip = instr.t;
 660 
 661 		} // end if down
 662 	}
 663 
 664 	/* 
 665 	 * Handle Subroutine instruction
 666 	 * i.subr(target)	  : fork to ROUTINE, store LEFT in result.
 667 	 *
 668 	 * Handle the subroute call, steps 3 through 7
 669 	 * ENTRY
 670 	 * 1- Hit i.scope (emitted)
 671 	 * 2- Instantiate parameters (emitted)
 672 	 * 3- Instantiate return variable as defined in i.subr instruction
 673 	 * 4- Toss i.subr instruction on the stack
 674 	 * 5- Toss Instruction Pointer on the stack
 675 	 * 6- Toss reference to currently runninng executable (VMExec.) on stack
 676 	 * 7- Load new SUBR and let run
 677 	 *
 678 	 * 1, 2 are emitted instructions, as such:
 679 	 * <subroutine>	i.scope				// do 1
 680 	 *		(SET)*						// do 2
 681 	 *		i.subr(name)				// do 3 through 13
 682 	 *		i.rscope					// do 14
 683 	 *		if (result exist) i.store(result)       // do 15
 684 	 */
 685 	private void entry_subr(VMISubr instr) throws VMException {
 686 
 687 		myLog.debug("SIMVM: Enter subr.  t=" + instr.t);
 688 
 689 		VMExecutable loadedSim = null;
 690 
 691 		try {
 692 
 693 			//	Make sure we can load it
 694 			loadedSim = loader.load(instr.t);
 695 
 696 		} catch (Exception e) {
 697 
 698 			// This error is recoverable
 699 			left = Constants.EMPTY_LEFT;
 700 			ip++;
 701 			throw new VMException(
 702 				"Failed to load subroutine= "
 703 					+ instr.t
 704 					+ ".  Aborting call.  Reason="
 705 					+ e.getMessage(),
 706 				AutohitErrorCodes.CODE_VM_INSTRUCTION_ABORT,
 707 				e);
 708 		}
 709 
 710 		// Any error after here is catastrophic
 711 		try {
 712 			// 3- Instantiate return variable as defined in i.subr instruction
 713 			if ((loadedSim.output != null)
 714 				&& (loadedSim.output.name != null)) {
 715 				core.store(loadedSim.output.name, Constants.EMPTY_LEFT);
 716 			}
 717 
 718 			// 4- Toss i.subr instruction on the stack
 719 			core.push(instr);
 720 
 721 			// 5- Toss Instruction Pointer on the stack
 722 			core.push(new Integer(ip));
 723 
 724 			// 6- Toss reference to currently runninng executable (VMExec.) on stack
 725 			core.push(mySim);
 726 
 727 			// 7- Load new SUBR and let run
 728 			mySim = loadedSim;
 729 			subDepth++;
 730 			ip = 0;
 731 
 732 		} catch (Exception e) {
 733 			throw new VMException(
 734 				"SUBROUTINE Subsystem fault.  Unable to complete SUBR. error="
 735 					+ e.getMessage(),
 736 				VMException.CODE_VM_SUBSYSTEM_FAULT,
 737 				e);
 738 		}
 739 	}
 740 
 741 	/*
 742 	 * Handle the subroutine exit, steps 10 through 11
 743 	 * EXIT
 744 	 * 10- Pop the IP (in the Integer)
 745 	 * 11- Pop the i.subr.  Read the i.subr.return, put in left, remove from core.
 746 	 */
 747 	private void exit_subr() throws VMException {
 748 		try {
 749 
 750 			myLog.debug("SIMVM: Exit subr.");
 751 
 752 			// 10- Pop the IP (in the Integer)
 753 			Integer ipObj = (Integer) core.pop();
 754 			ip = ipObj.intValue();
 755 
 756 			// 11- Pop the i.subr.  Read the vmexec.output.name, put in left.
 757 			core.pop();
 758 			if ((mySim.output != null)&&(mySim.output.name != null)) {
 759 				left = (String) core.fetch(mySim.output.name);
 760 				if (left == null) {
 761 					left = Constants.EMPTY_LEFT;
 762 					myLog.warning(
 763 						"SUBR EXIT: Expected a return value for "
 764 							+ mySim.output.name
 765 							+ " but it was null.");
 766 				}
 767 			} else {
 768 				left = Constants.EMPTY_LEFT;
 769 			}
 770 
 771 			// Unwrap
 772 			subDepth--;
 773 
 774 		} catch (Exception e) {
 775 			throw new VMException(
 776 				"Software Detected Fault: Failed subroutine context switch. error="
 777 					+ e.getMessage(),
 778 				VMException.CODE_VM_SOFTWARE_DETECTED_FAULT,
 779 				e);
 780 		}
 781 	}
 782 
 783 	/* 
 784 	 * Handle eval Instruction
 785 	 * i.eval(literal)	  : evaluate and store in LEFT(literal)
 786 	 */
 787 	private void handleEval(VMIEval instr) throws VMException {
 788 
 789 		try {
 790 
 791 			left = StringProcessors.evalString2Core(instr.e, core);
 792 
 793 		} catch (Exception e) {
 794 			left = Constants.EMPTY_LEFT;
 795 
 796 			throw new VMException(
 797 				"Evaluation error.  Unable to complete EVAL on ["
 798 					+ instr.e
 799 					+ "].  error="
 800 					+ e.getMessage(),
 801 				AutohitErrorCodes.CODE_PROGRAM_ERROR,
 802 				e);
 803 
 804 		}
 805 	}
 806 
 807 	/**
 808 	 * Handle Math Instruction<code>
 809 	 * If either left or right fail a parse, the values will be compared
 810 	 * as strings.
 811 	 * i.math(oper)  	  : execute operation from RIGHT(literal)</code>
 812 	 */
 813 	private void handleMath(VMIMath instr) throws VMException {
 814 
 815 		int res = 0;
 816 		int l = 0;
 817 		int r = 0;
 818 		boolean donumbers = true;
 819 
 820 		myLog.debug(
 821 			"MATH start: left="
 822 				+ left.toString()
 823 				+ "  right="
 824 				+ right.toString());
 825 
 826 		try {
 827 
 828 			try {
 829 				l = Integer.parseInt(left.toString());
 830 			} catch (NumberFormatException e) {
 831 				donumbers = false;
 832 			}
 833 			try {
 834 				r = Integer.parseInt(right.toString());
 835 			} catch (NumberFormatException e) {
 836 				donumbers = false;
 837 			}
 838 
 839 			if (donumbers) {
 840 
 841 				// treat like numeric arthimatic
 842 				if (instr.o.charAt(0) == Constants.MATH_PLUS) {
 843 					res = l + r;
 844 
 845 				} else if (instr.o.charAt(0) == Constants.MATH_MINUS) {
 846 					res = l - r;
 847 
 848 				} else if (instr.o.charAt(0) == Constants.MATH_DIVIDE) {
 849 
 850 					try {
 851 						res = l / r;
 852 					} catch (ArithmeticException e) {
 853 						dumpei(instr);
 854 						res = 0;
 855 						throw new VMException(
 856 							"DIVIDE BY ZERO.  Math operation aborted; result = 0.  Operation="
 857 								+ instr.o,
 858 							AutohitErrorCodes.CODE_PROGRAM_DIVIDEBYZERO,
 859 							e);
 860 					}
 861 
 862 				} else if (instr.o.charAt(0) == Constants.MATH_MULTIPLY) {
 863 					res = r * l;
 864 
 865 				} else if (instr.o.charAt(0) == SimLanguage.cEQ_OPERATION) {
 866 					res = r - l;
 867 
 868 				} else {
 869 					dumpei(instr);
 870 					throw new VMException(
 871 						"Unrecognized math operations.  Math operation aborted; result = 0.  Operation="
 872 							+ instr.o,
 873 						AutohitErrorCodes.CODE_PROGRAM_ERROR);
 874 
 875 				}
 876 
 877 			} else if (right instanceof String) {
 878 				// treat like a string compare.  Compares two strings lexicographically.
 879 				// "0" means the strings are identical
 880 				res = ((String) right).compareTo(left.toString());
 881 
 882 			} else {
 883 				throw new VMException(
 884 					"Math error.  Left and/or Right expressions inappropriate objects for a math operation.",
 885 					VMException.CODE_VM_INSTRUCTION_ERROR);
 886 			}
 887 
 888 		} catch (VMException ve) {
 889 			throw ve;
 890 		} catch (Exception e) {
 891 			throw new VMException(
 892 				"Math fault.  Unable to complete MATH.  error="
 893 					+ e.getMessage(),
 894 				VMException.CODE_VM_INSTRUCTION_FAULT,
 895 				e);
 896 		}
 897 
 898 		// store result
 899 		left = Integer.toString(res);
 900 
 901 		myLog.debug("MATH done: left=" + left);
 902 	}
 903 
 904 	/**
 905 	 * Handle merge instruction<p><code>
 906 	 * i.merge(buffer)   : merge LEFT(literal) with named buffer</code>
 907 	 */
 908 	private void handleMerge(VMIMerge instr) throws VMException {
 909 
 910 		try {
 911 
 912 			Object tb = core.fetch(instr.b);
 913 
 914 			if (tb != null) {
 915 
 916 				if (tb instanceof StringBuffer) {
 917 
 918 					((StringBuffer) tb).append(left);
 919 
 920 				} else {
 921 					throw new VMException(
 922 						"VMSim: Merge not possible.  ["
 923 							+ instr.b
 924 							+ "] is not a buffer.  Aborting instruction, but not execution.",
 925 						AutohitErrorCodes.CODE_PROGRAM_ERROR);
 926 				}
 927 
 928 			} else {
 929 				throw new VMException(
 930 					"VMSim: Merge not possible.  ["
 931 						+ instr.b
 932 						+ "] does not exist.  Aborting instruction, but not execution.",
 933 					AutohitErrorCodes.CODE_PROGRAM_ERROR);
 934 			}
 935 
 936 		} catch (Exception e) {
 937 			throw new VMException(
 938 				"VMSim: Merge fault.  Unable to complete MERGE.  error="
 939 					+ e.getMessage(),
 940 				AutohitErrorCodes.CODE_VM_SOFTWARE_DETECTED_FAULT,
 941 				e);
 942 		}
 943 	}
 944 
 945 	/**
 946 	 * Handle If instruction<p><code>
 947 	 * i.if(literal,oper): Evaluate LEFT(literal).  0 means the evaluation was a match.  
 948 	 *      You can modify with an operationl, oper = (gt|lt|eq|not):
 949 	 *		eq  : default.   if LEFT(literal) is not 0, jump to literal
 950 	 *		gt  : if LEFT(literal) is =< 0, jump to literal
 951 	 *		lt  : if LEFT(literal) is >= 0, jump to literal
 952 	 *		not : if LEFT(literal) is 0, jump to literal</code>
 953 	 * TODO This using a non-zero as a default in IF resolution may be scary
 954 	 */
 955 	private void handleIf(VMIIf instr) throws VMException {
 956 
 957 		// Prepare Left.  Default value is not zero	
 958 		int leftval = SimLanguage.NOT_ZERO;
 959 		String scrubleft;
 960 
 961 		if (left instanceof String) {
 962 			scrubleft = (String) left;
 963 
 964 		} else if (left instanceof StringBuffer) {
 965 			scrubleft = ((StringBuffer) left).toString();
 966 		} else {
 967 			ip = ((VMIIf) ci).t;
 968 			throw new VMException(
 969 				"Left expression inappropriate type for IF operation.  Assume FALSE.",
 970 				VMException.CODE_VM_INSTRUCTION_ERROR);
 971 		}
 972 
 973 		try {
 974 			leftval = Integer.parseInt(scrubleft);
 975 		} catch (Exception e) {
 976 		} // Dont care
 977 
 978 		switch (instr.operFlag) {
 979 
 980 			case SimLanguage.EQ :
 981 				if (leftval == SimLanguage.ZERO) {
 982 					ip++; // success.  branch.
 983 				} else {
 984 					ip = ((VMIIf) ci).t; // Failed if
 985 				}
 986 				break;
 987 
 988 			case SimLanguage.LT :
 989 				if (leftval <= SimLanguage.ZERO) {
 990 					ip++; // success.  branch.
 991 				} else {
 992 					ip = ((VMIIf) ci).t; // Failed if
 993 				}
 994 				break;
 995 
 996 			case SimLanguage.GT :
 997 				if (leftval >= SimLanguage.ZERO) {
 998 					ip++; // success.  branch.
 999 				} else {
1000 					ip = ((VMIIf) ci).t; // Failed if
1001 				}
1002 				break;
1003 
1004 			case SimLanguage.NOT :
1005 				if (leftval != SimLanguage.ZERO) {
1006 					ip++; // success.  branch.
1007 				} else {
1008 					ip = ((VMIIf) ci).t; // Failed if
1009 				}
1010 				break;
1011 
1012 			default :
1013 				throw new VMException(
1014 					"Software Detected Fault.  IF with an unknown oper.  This should never happen.",
1015 					VMException.CODE_VM_SOFTWARE_DETECTED_FAULT);
1016 		}
1017 
1018 	}
1019 
1020 	/**
1021 	 * Handle Assert instruction<p><code>
1022 	 * i.if(literal,oper): Evaluate LEFT(literal).  If it is empty, null, or void, the check passes
1023 	 * 	    and the IP should move to the next instruction.  If the check fails,
1024 	 *      then the IP should be set to the target.
1025 	 *      You can modify with an operationl, oper = (eq|not):
1026 	 *		eq  : default.   if LEFT(literal) is empty, null, or void, then jump to literal
1027 	 *		not : if LEFT(literal) is NOT empty, null, or void, jump to literal</code>
1028 	 */
1029 	private void handleAssert(VMIAssert instr) throws VMException {
1030 
1031 		// See if it is empty
1032 		boolean isempty = false;
1033 		if (left == null) {
1034 			isempty = true;
1035 		} else if (left instanceof String) {
1036 			if (((String) left).equals(Constants.EMPTY_LEFT)
1037 				|| ((String) left).length() < 1) {
1038 				isempty = true;
1039 			}
1040 		}
1041 
1042 		switch (instr.operFlag) {
1043 
1044 			// ASSERT THERE IS SOMETHING
1045 			case SimLanguage.EQ :
1046 				if (isempty) {
1047 					// It is empty.  Assert failed
1048 					ip = ((VMIAssert) ci).t;
1049 				} else {
1050 					// It is not empty.  Assert succeeded.
1051 					ip++;
1052 				}
1053 				break;
1054 
1055 				// ASSERT THERE IS NOT SOMETHING
1056 			case SimLanguage.NOT :
1057 				if (isempty) {
1058 					// It is empty.  Assert succeeded.
1059 					ip++;
1060 				} else {
1061 					// It is not empty.  Assert failed
1062 					ip = ((VMIAssert) ci).t;
1063 				}
1064 				break;
1065 
1066 			default :
1067 				throw new VMException(
1068 					"Software Detected Fault.  ASSERT with an unknown or unsupported operation.  This should never happen.  oper="
1069 						+ instr.operFlag,
1070 					VMException.CODE_VM_SOFTWARE_DETECTED_FAULT);
1071 		} // end case
1072 	}
1073 
1074 	/* 
1075 	 * Handle merge instruction
1076 	 * i.reduce(buffer)  : reduce a buffer and put in LEFT(literal)
1077 	 */
1078 	private void handleReduce(VMIReduce instr) throws VMException {
1079 
1080 		try {
1081 
1082 			Object tb = core.fetch(instr.b);
1083 
1084 			if (tb != null) {
1085 
1086 				if (tb instanceof StringBuffer) {
1087 
1088 					left = tb.toString();
1089 
1090 				} else {
1091 					throw new VMException(
1092 						"VMSim: Reduce not possible.  ["
1093 							+ instr.b
1094 							+ "] is not a buffer.  Aborting instruction, but not execution.",
1095 						AutohitErrorCodes.CODE_PROGRAM_ERROR);
1096 				}
1097 
1098 			} else {
1099 				throw new VMException(
1100 					"Reduce not possible.  ["
1101 						+ instr.b
1102 						+ "] does not exist.  Aborting instruction, but not execution.",
1103 					AutohitErrorCodes.CODE_PROGRAM_ERROR);
1104 			}
1105 
1106 		} catch (Exception e) {
1107 			throw new VMException(
1108 				"Reduce fault.  Unable to complete REDUCE.  error="
1109 					+ e.getMessage(),
1110 				AutohitErrorCodes.CODE_VM_SOFTWARE_DETECTED_FAULT,
1111 				e);
1112 		}
1113 	}
1114 
1115 	/*
1116 	 * LOG/Message Helper. -- DUMP
1117 	 */
1118 	private void dumpei(VMInstruction i) {
1119 		if ((myLog.debugState() == false) || (ci == null))
1120 			return;
1121 		myLog.debug("VMSim: DUMP instruction pointer.  ip=" + ip);
1122 		myLog.debug("VMSim: DUMP errored instruction.   i=" + i.toString());
1123 		myLog.debug("VMSim: DUMP left.  l=[" + left + "]");
1124 		myLog.debug("VMSim: DUMP right. l=[" + right + "]");
1125 		myLog.debug("VMSim: DUMP state. state=" + state);
1126 		myLog.debug(
1127 			"VMSim: DUMP running program="
1128 				+ mySim.name
1129 				+ "v"
1130 				+ mySim.major
1131 				+ "."
1132 				+ mySim.minor);
1133 	}
1134 
1135 }