package ij.macro;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.plugin.Macro_Runner;
import ij.plugin.frame.*;
import ij.util.Tools;
import ij.text.*;
import ij.measure.ResultsTable;
import java.awt.*;
import java.util.*;
import java.awt.event.KeyEvent;
import java.io.PrintWriter;


/** This is the recursive descent parser/interpreter for the ImageJ macro language. */
public class Interpreter implements MacroConstants {

	static final int STACK_SIZE = 1000;
	static final int MAX_ARGS = 20;

	int pc;
	int token;
	int tokenAddress;
	double tokenValue;
	String tokenString;
	boolean looseSyntax = true;
	int lineNumber;
	boolean statusUpdated;
	boolean showingProgress;
	boolean keysSet;
	boolean checkingType;
	int prefixValue;
	
	Variable[] stack;
	int topOfStack = -1;
	int topOfGlobals = -1;
	int startOfLocals = 0;

	static volatile Interpreter instance, previousInstance;
	public static boolean batchMode;
	static Vector imageTable; // images opened in batch mode
	static Vector imageActivations; // images ordered by activation time
	volatile boolean done;
	Program pgm;
	Functions func;
	boolean inFunction;
	String macroName;
	String argument;
	String returnValue;
	boolean calledMacro; // macros envoked by eval() or runMacro()
	boolean batchMacro; // macros envoked by Process/Batch commands
	double[] rgbWeights;
	boolean inPrint;
	static String additionalFunctions;
	Debugger debugger;
	int debugMode = Debugger.NOT_DEBUGGING;
	boolean showDebugFunctions;
	static boolean showVariables;
	boolean wasError;
	ImagePlus batchMacroImage;
	boolean inLoop;
	int loopDepth;
	static boolean tempShowMode;
	boolean waitingForUser;
	
	static TextWindow arrayWindow;
	int inspectStkIndex = -1;
	int inspectSymIndex = -1;
	boolean evaluating;
	ResultsTable applyMacroTable;
	int errorCount;
	volatile boolean ignoreErrors;
	String errorMessage;
	String evalOutput;
	int[] callStack;
	int callDepth = 0;
	
	/** Interprets the specified string. */
	public void run(String macro) {
		if (additionalFunctions!=null) {
			if (!(macro.endsWith("\n")|| additionalFunctions.startsWith("\n")))
				macro = macro + "\n" + additionalFunctions;
			else
				macro = macro + additionalFunctions;
		}
		IJ.resetEscape();
		Tokenizer tok = new Tokenizer();
		Program pgm = tok.tokenize(macro);
		if (pgm.hasVars && pgm.hasFunctions)
			saveGlobals2(pgm);
		run(pgm);
	}
	
	/** Runs the specified macro, passing it a string 
	 * argument and returning a string value.
	 * @see ij.IJ#runMacro(String,String)
	 * @see ij.IJ#runMacroFile(String,String)
	*/
	public String run(String macro, String arg) {
		argument = arg;
		calledMacro = true;
		if (IJ.getInstance()==null)
			setBatchMode(true);
		Interpreter saveInstance = instance;
		run(macro);
		instance = saveInstance;
		return returnValue;
	}
	
	/** Evaluates 'code' and returns the output, or any error, as a String.
	 * @see ij.Macro#eval
	*/
	public String eval(String code) {
		Interpreter saveInstance = instance;
		if (pgm!=null)
			reuseSymbolTable();
		Tokenizer tok = new Tokenizer();
		Program pgm = tok.tokenize(code);
		if (pgm.hasVars && pgm.hasFunctions)
			saveGlobals2(pgm);
		evaluating = true;
		evalOutput = null;
		ignoreErrors = true;
		calledMacro = true;
		run(pgm);
		instance = saveInstance;
		if (errorMessage!=null)
			return errorMessage;
		else
			return evalOutput;
	}
	
	private void reuseSymbolTable() {
		if (pgm==null)
			return;
		Symbol[] table1 = pgm.getSymbolTable();
		Symbol[] table2 = new Symbol[pgm.stLoc+1];
		for (int i=0; i<=pgm.stLoc; i++)
			table2[i] = table1[i];
		Program.systemTable = table2;
	}
	
	/** Interprets the specified tokenized macro file starting at location 0. */
	public void run(Program pgm) {
		this.pgm = pgm;
		pc = -1;
		callDepth = 0;
		instance = this;
		if (!calledMacro) {
			batchMode = false;
			imageTable = imageActivations = null;
		}
		pushGlobals();
		if (func==null)
			func = new Functions(this, pgm);
		else
			func.pgm = pgm;
		func.plot = null;
		done = false;
		errorMessage = null;
		doStatements();
		finishUp();
	}

	/** Runs an existing macro starting at the specified program counter location. */
	public void run(int location) {
		topOfStack = topOfGlobals;
		done = false;
		pc = location-1;
		doStatements();
	}

	/** Interprets the specified tokenized macro starting at the specified location. */
	public void runMacro(Program pgm, int macroLoc, String macroName) {
		calledMacro = true;
		this.pgm = pgm;
		this.macroName = macroName;
		pc = macroLoc-1;
		previousInstance = instance;
		instance = this;
		pushGlobals();
		if (func==null)
			func = new Functions(this, pgm);
		func.plot = null;
		if (macroLoc==0)
			doStatements();
		else
			doBlock(); 
		finishUp();
		Recorder.recordInMacros = false;
	}
	
	/** Runs Process/Batch/ macros. */
	public ImagePlus runBatchMacro(String macro, ImagePlus imp) {
		calledMacro = true;
		batchMacro = true;
		setBatchMode(true);
		addBatchModeImage(imp);
		batchMacroImage = null;
		run(macro);
		IJ.showStatus("");
		return batchMacroImage;
	}

	/** Saves global variables. */
	public void saveGlobals(Program pgm) {
		Interpreter saveInstance = instance;
		saveGlobals2(pgm);
		instance = saveInstance; 
	}
	
	void saveGlobals2(Program pgm) {
		this.pgm = pgm;
		pc = -1;
		instance = this;
		func = new Functions(this, pgm);
		while (!done) {
			getToken();
			switch (token) {
				case VAR: doVar(); break;
				case MACRO: skipMacro(); break;
				case FUNCTION: skipFunction(); break;
				default:
			}
		}
		instance = null;
		pgm.saveGlobals(this);
		pc = -1;
		topOfStack = -1;
		done = false;
	}

	final void getToken() {
		if (done) return;
		token = pgm.code[++pc];
		//IJ.log(pc+" "+pgm.decodeToken(token));
		if (token<=127)
			return;
		tokenAddress = token>>TOK_SHIFT;
		token = token&TOK_MASK;
		Symbol sym = pgm.table[tokenAddress];
		tokenString = sym.str;
		tokenValue = sym.value;
		done = token==EOF;
	}

	final int nextToken() {
		return pgm.code[pc+1]&TOK_MASK;
	}

	final int nextNextToken() {
		return pgm.code[pc+2]&TOK_MASK;
	}

	final void putTokenBack() {
		pc--;
		if (pc<0)
			pc = -1;
		if (token==EOF)
			done = false;
	}

	void doStatements() {
		while (!done)
			doStatement();
	}

	final void doStatement() {
		getToken();
		if (debugMode!=Debugger.NOT_DEBUGGING && debugger!=null && !done && token!=';' && token!=FUNCTION) {
			debugger.debug(this, debugMode);
			if (done) return;
		}
		switch (token) {
			case VAR:
				doVar();
				break;
			case PREDEFINED_FUNCTION:
				func.doFunction(pgm.table[tokenAddress].type);
				break;
			case USER_FUNCTION:
				runUserFunction();
				break;
			case RETURN:
				doReturn();
				break;
			case BREAK:
				if (inLoop) throw new MacroException(BREAK);
				break;
			case CONTINUE:
				if (inLoop) throw new MacroException(CONTINUE);
				break;
			case WORD:
				if (pgm.code[pc+1]=='.')
					doStringFunction();
				else
					doAssignment();
				break;
			case IF:
				doIf();
				return;
			case ELSE:
				error("Else without if");
				return;
			case FOR:
				doFor();
				return;
			case WHILE:
				doWhile();
				return;
			case DO:
				doDo();
				return;
			case MACRO:
				runFirstMacro();
				return;
			case FUNCTION:
				skipFunction();
				return;
			case ';':
				return;
			case '{':
				putTokenBack();
				doBlock();
				return;
			case NUMBER:
			case NUMERIC_FUNCTION:
			case STRING_FUNCTION:
			case STRING_CONSTANT:
			case VARIABLE_FUNCTION:
			case '(': 
				putTokenBack();
				inPrint = true;
				String s = getString();
				inPrint = false;
				if (s!=null && s.length()>0 && !s.equals("NaN") && !s.equals("[aborted]"))
					log(s);
				return;
			case ARRAY_FUNCTION: func.getArrayFunction(pgm.table[tokenAddress].type); break;
			case EOF: break;
			default:
				if (evaluating && token==PI) {
					putTokenBack();
					log(""+getExpression());
				} else
					error("Statement cannot begin with '"+pgm.decodeToken(token, tokenAddress)+"'");
		}
		if (!looseSyntax) {
			getToken();
			if (token!=';' && !done)
				error("';' expected");
		}
	}
	
	private void doStringFunction() {
		boolean stringFunction = (pgm.code[pc+2]&0xff)==STRING_FUNCTION;
		putTokenBack();
		String s = stringFunction?getString():""+getExpression();
		if (s.endsWith(".0"))
			s = s.substring(0,s.length()-2);
		IJ.log(s);
	}
	
	void log(String s) {
		if (evaluating)
			evalOutput = s;
		else
			IJ.log(s);
	}
	
	//For showing call stack in macro errors
	private void growCallStack(int grow) {
		if(callStack == null){
			callStack = new int[10];
			callDepth = 0;
		}
		if (callDepth <0 ||  callDepth > 8)
			return;
		if (grow == 1){
			int line = pgm.lineNumbers[pc];
			callStack[callDepth++] = line;
		}
		if (grow == -1 && callDepth > 0)
			callDepth--;
	}
	
	Variable runUserFunction() {
		growCallStack(1);
		int newPC = (int)tokenValue;
		int saveStartOfLocals = startOfLocals;
		startOfLocals = topOfStack+1;
		int saveTOS = topOfStack;		
		int nArgs = pushArgs();
		int savePC = pc;
		Variable value = null;
		pc = newPC;
		setupArgs(nArgs);
		boolean saveInFunction = inFunction;
		inFunction = true;
		try {
			doBlock();
		} catch (ReturnException e) {
			value = new Variable(0, e.value, e.str, e.array);
			if (value.getArray()!=null && e.arraySize!=0)
				value.setArraySize(e.arraySize);
		}
		inFunction = saveInFunction;
		pc = savePC;
		trimStack(saveTOS, saveStartOfLocals);
		growCallStack(-1);
		return value;
	}

	/** Push function arguments onto the stack. */
	int pushArgs() {
		getLeftParen();
		int count = 0;
		Variable[] args = new Variable[MAX_ARGS];
		double value;
		if (nextToken()!=')') {
			do {
				if (count==MAX_ARGS)
					error("Too many arguments");
				int next = nextToken();
				int nextPlus = pgm.code[pc+2]&0xff;
				if (next==STRING_CONSTANT || next==STRING_FUNCTION)
					args[count] = new Variable(0, 0.0, getString());
				else if (next==USER_FUNCTION) {
					int savePC = pc;
					getToken(); // the function
					boolean simpleFunctionCall = isSimpleFunctionCall(false);
					pc = savePC;
					if (simpleFunctionCall) {
						getToken(); // the function
						Variable v2 = runUserFunction();
						if (v2==null)
							error("No return value");
						args[count] = v2;
					} else
						args[count] = new Variable(0, getExpression(), null);	
				} else if (next==WORD && (nextPlus==','||nextPlus==')')) {
					value = 0.0;
					Variable[] array = null;
					int arraySize = 0;
					String str = null;
					getToken();
					Variable v = lookupVariable();
					if (v!=null) {
						int type = v.getType();
						if (type==Variable.VALUE)
							value = v.getValue();
						else if (type==Variable.ARRAY) {
							array = v.getArray();
							arraySize = v.getArraySize();
						} else
							str = v.getString();
					}
					args[count] = new Variable(0, value, str, array);
					if (array!=null) args[count].setArraySize(arraySize);
				} else if (next==WORD && nextPlus=='[' ) {
					int savePC = pc;
					getToken();
					Variable v = lookupVariable();
					v = getArrayElement(v);
					if (v.getString()!=null)
						args[count] = new Variable(0, 0.0, v.getString(), null);
					else {
						pc = savePC;
						args[count] = new Variable(0, getExpression(), null);
					}
				} else if (next==WORD && nextPlus=='.' ) { // s.length, s.substring(), etc.
					boolean stringFunction = (pgm.code[pc+3]&0xff)==136;
					if (stringFunction)
						args[count] = new Variable(0, 0.0, func.getString(), null);
					else
						args[count] = new Variable(0, getExpression(), null);
				} else if (nextPlus=='+' && next==WORD) {
					int savePC = pc;
					getToken();
					Variable v = lookupVariable();
					boolean isString = v!=null && v.getType()==Variable.STRING;
					pc = savePC;
					if (isString)
						args[count] = new Variable(0, 0.0, getString());
					else
						args[count] = new Variable(0, getExpression(), null);
				} else if (next==ARRAY_FUNCTION) {
					getToken();
					Variable[] array = func.getArrayFunction(pgm.table[tokenAddress].type);
					args[count] = new Variable(0, 0, null, array);
				} else
					args[count] = new Variable(0, getExpression(), null);
				count++;
				getToken();
			} while (token==',');
			putTokenBack();
		}
		int nArgs = count;
		while(count>0)
			push(args[--count], this);
		getRightParen();
		return nArgs;
	}

	void setupArgs(int nArgs) {
		getLeftParen();
		int i = topOfStack;
		int count = nArgs;
		if (nextToken()!=')') {
			do {
			   getToken();
			   if (i>=0)
				  stack[i].symTabIndex = tokenAddress;
			   i--;
			   count--;
			   getToken();
			} while (token==',');
			putTokenBack();
		}
		if (count!=0)
		   error(nArgs+" argument"+(nArgs==1?"":"s")+" expected");
		getRightParen();
	}
	
	// cache exception object for better performance
    ReturnException returnException;
    
    // Handle return statement 
	void doReturn() {
		double value = 0.0;
		String str = null;
		Variable[] array = null;
		int arraySize = 0;
		getToken();		
		if (token=='(') {
			int next = pgm.code[pc+1];
			if ((next&TOK_MASK)==STRING_CONSTANT || (next&TOK_MASK)==STRING_FUNCTION || isString(pc+1))
				error("String enclosed in parens");
		}		
		if (token!=';') {
			boolean isString = token==STRING_CONSTANT || token==STRING_FUNCTION;
			boolean isArrayFunction = token==ARRAY_FUNCTION;
			if (token==WORD) {
				Variable v = lookupLocalVariable(tokenAddress);
				if (v!=null && nextToken()==';') {
					array = v.getArray();
					if (array!=null) arraySize=v.getArraySize();
					isString = v.getString()!=null;
				} else if (v!=null && nextToken()=='+')
					isString = v.getType()==Variable.STRING;
			}
			putTokenBack();
			if (isString)
				str = getString();
			else if (isArrayFunction) {
				getToken();
				array = func.getArrayFunction(pgm.table[tokenAddress].type);
			} else if (array==null) {
				if ((pgm.code[pc+2]&0xff)=='[' && nextToken()==WORD) {
					int savePC = pc;
					getToken();
					Variable v = lookupVariable();
					v = getArrayElement(v);
					pc = savePC;
					if (v.getString()!=null)
						str = getString();
					else
						value = getExpression();
				} else
					value = getExpression();
			}
		}
		if (inFunction) {
			if (returnException==null)
				returnException = new ReturnException();
			returnException.value = value;
			returnException.str = str;
			returnException.array = array;
			returnException.arraySize = arraySize;
			//throw new ReturnException(value, str, array);
			throw returnException;
		} else {
			finishUp();
			if (value!=0.0 || array!=null)
				error("Macros can only return strings");
			returnValue = str;
			done = true;
		}
	}
	
	void doFor() {
		boolean saveLooseSyntax = looseSyntax;
		looseSyntax = false;
		loopDepth++;
		inLoop = true;
		getToken();
		if (token!='(')
			error("'(' expected");
		getToken(); // skip 'var'
		if (token!=VAR)
			putTokenBack();
		do {
			if (nextToken()!=';')
			   getAssignmentExpression();
			getToken();
		} while (token==',');
		if (token!=';')
			error("';' expected");
		int condPC = pc;
		int incPC2, startPC=0;
		double cond = 1;
		while (true) {
			if (pgm.code[pc+1]!=';')
			   cond = getLogicalExpression();
			if (startPC==0)
				checkBoolean(cond);
			getToken();
			if (token!=';')
				error("';' expected");
			int incPC = pc;
			// skip to start of code
			if (startPC!=0)
				pc = startPC;
			else {
			  while (token!=')') {
				getToken();
				//IJ.log(pgm.decodeToken(token,tokenAddress));
				if (token=='{' || token==';' || token=='(' || done)
					error("')' expected");
			   }
			}
			startPC = pc;
			if (cond==1) {
				try {
					doStatement();
				} catch(MacroException e) {
					if (e.getType()==BREAK) {
						pc = startPC;
						skipStatement();
						break;
					}
				}
			} else {
				skipStatement();
				break;
			}
			pc = incPC; // do increment
			do {
				 if (nextToken()!=')')
					getAssignmentExpression();
				getToken();
			} while (token==',');
			pc = condPC;
		}
		looseSyntax = saveLooseSyntax;
		loopDepth--;
		if (loopDepth==0)
			inLoop = false;
	}

	void doWhile() {
		looseSyntax = false;
		loopDepth++;
		inLoop = true;
		int savePC = pc;
		boolean isTrue;
		do {
			pc = savePC;
			isTrue = getBoolean();
			if (isTrue) {
				try {
					doStatement();
				} catch(MacroException e) {
					if (e.getType()==BREAK) {
						pc = savePC;
						getBoolean();
						skipStatement();
						break;
					}
				}
			} else
				skipStatement();
		} while (isTrue && !done);
		loopDepth--;
		if (loopDepth==0)
			inLoop = false;
	}

	void doDo() {
		looseSyntax = false;
		int savePC = pc;
		boolean isTrue;
		do {
			doStatement();
			getToken();
			if (token!=WHILE)
				error("'while' expected");
			isTrue = getBoolean();
			if (isTrue)
				pc = savePC;
		} while (isTrue && !done);
	}

	final void doBlock() {
		getToken();
		if (token!='{')
			error("'{' expected");
		while (!done) {
			getToken();
			if (token=='}')
				break;
			putTokenBack();
			doStatement();
		}
		if (token!='}')
			error("'}' expected");
	}

	final void skipStatement() {
		if (done)
			return;
		getToken();
		switch (token) {
			case PREDEFINED_FUNCTION: case USER_FUNCTION: case VAR:
			case WORD: case '(': case PLUS_PLUS: case RETURN: case ARRAY_FUNCTION:
			case NUMERIC_FUNCTION: case STRING_FUNCTION: case VARIABLE_FUNCTION:
				skipSimpleStatement();
				break;
			case IF:
				skipParens();
				skipStatement();
				getToken();
				if (token==ELSE)
					skipStatement();
				else
					putTokenBack();
				break;
			case FOR:
				skipParens();
				skipStatement();
				break;
			case WHILE:
				skipParens();
				skipStatement();
				break;
			case DO:
				skipStatement();
				getToken(); // skip 'while'
				skipParens();
				break;
			case BREAK: case CONTINUE: case ';':
				break;
			case '{':
				putTokenBack();
				skipBlock();
				break;
			default:
				error("Skipped statement cannot begin with '"+pgm.decodeToken(token, tokenAddress)+"'");
		}
	}

	final void skipBlock() {
		int count = 0;
		do {
			getToken();
			if (token=='{')
				count++;
			else if (token=='}')
				count--;
			else if (done) {
				error("'}' expected");
				return;
			}
		} while (count>0);
	}
	
	final void skipParens() {
		int count = 0;
		do {
			getToken();
			if (token=='(')
				count++;
			else if (token==')')
				count--;
			else if (done) {
				error("')' expected");
				return;
			}
		} while (count>0);
	}

	final void skipSimpleStatement() {
		boolean finished = done;
		getToken();
		while (!finished && !done) {
			if (token==';')
				finished = true;
			else if (token==ELSE||(token==PREDEFINED_FUNCTION&&pgm.code[pc-1]!='.'))
				error("';' expected");
			else
				getToken();
		}
	}

	/** Skips a user-defined function. */
	void skipFunction() {
		getToken(); // skip function id
		skipParens();
		skipBlock();
	}

	void runFirstMacro() {
		getToken(); // skip macro label
		doBlock(); 
		done = true;
		finishUp();
	}

	void skipMacro() {
		getToken(); // skip macro label
		skipBlock();
	}

	final void doAssignment() {
		int next = pgm.code[pc+1]&0xff;
		if (next=='[') {
			doArrayElementAssignment();
			return;
		} 
		int type = getExpressionType();
		switch (type) {
			case Variable.STRING: doStringAssignment(); break;
			case Variable.ARRAY: doArrayAssignment(); break;
			case USER_FUNCTION: doUserFunctionAssignment(); break;
			case STRING_FUNCTION: doNumericStringAssignment(); break;
			default:
				putTokenBack();
				double value = getAssignmentExpression();
				if (evaluating) log(""+value);
		}
	}

	int getExpressionType() {
		int rightSideToken = pgm.code[pc+2];
		int tok = rightSideToken&0xff;
		if (tok==STRING_CONSTANT)
			return Variable.STRING;
		if (tok==STRING_FUNCTION) {
			int address = rightSideToken>>TOK_SHIFT;
			int type = pgm.table[address].type;
			if (type==DIALOG) {
				int token2 = pgm.code[pc+4];
				String name = pgm.table[token2>>TOK_SHIFT].str;
				if (name.equals("getNumber") || name.equals("getCheckbox"))
					return STRING_FUNCTION; 
			} else if (type==FILE) {
				int token2 = pgm.code[pc+4];
				String name = pgm.table[token2>>TOK_SHIFT].str;
				if (name.equals("exists")||name.equals("isDirectory")||name.equals("length")
				||name.equals("getLength")||name.equals("rename")||name.equals("delete"))
					return STRING_FUNCTION;
			} else if (type==LIST) {
				int token2 = pgm.code[pc+4];
				String name = pgm.table[token2>>TOK_SHIFT].str;
				if (name.equals("getValue")) return STRING_FUNCTION;
			} else if (numericStringFunction(pc+2))
				return Variable.VALUE;
			return Variable.STRING;
		}
		if (tok==ARRAY_FUNCTION)
			return Variable.ARRAY;
		if (tok==USER_FUNCTION)
			return USER_FUNCTION;
		if (tok==VARIABLE_FUNCTION) {
			int address = rightSideToken>>TOK_SHIFT;
			int type = pgm.table[address].type;
			if (isString(pc+2))
				return Variable.STRING;
			int token2 = pgm.code[pc+4];
			String name = pgm.table[token2>>TOK_SHIFT].str;
			if (name.equals("getColumn")||name.equals("toArray"))
				return Variable.ARRAY;			
		}
		if (tok!=WORD)
			return Variable.VALUE;
		Variable v = lookupVariable(rightSideToken>>TOK_SHIFT);
		if (v==null)
			return Variable.VALUE;
		int type = v.getType();
		if (type==Variable.VALUE)
			return Variable.VALUE;
		else if (type==Variable.STRING) {
			if (isString(pc+2))
				return Variable.STRING;
			else 
				return Variable.VALUE;
		}
		if (pgm.code[pc+3]=='.')
			return Variable.VALUE;		
		if (pgm.code[pc+3]!='[')
			return Variable.ARRAY;
		int savePC = pc;
		getToken(); //"="
		getToken(); //the variable
		checkingType = true;
		int index = getIndex();
		checkingType = false;
		pc = savePC-1;
		getToken();
		Variable[] array = v.getArray();
		if (index<0 || index>=array.length)
			return Variable.VALUE;
		return array[index].getType();
	}
	
	/** Handles string functions such as Dialog.getNumber() that return a number. */
	final void doNumericStringAssignment() {
		putTokenBack();
		getToken();		
		Variable v = lookupLocalVariable(tokenAddress);
		if (v==null) v = push(tokenAddress, 0.0, null, this);
		getToken();
		if (token!='=') error("'=' expected");
		v.setValue(getExpression());
	}

	final void doArrayElementAssignment() {
		Variable v = lookupLocalVariable(tokenAddress);
		if (v==null)
				error("Undefined identifier");
		if (pgm.code[pc+5]==';'&&(pgm.code[pc+4]==PLUS_PLUS||pgm.code[pc+4]==MINUS_MINUS))
			{putTokenBack(); getFactor(); return;}
		int index = getIndex();
		int expressionType = getExpressionType();
		if (expressionType==Variable.ARRAY) 
			error("Arrays of arrays not supported");
		getToken();
		int op = token;
		if (!(op=='='||op==PLUS_EQUAL||op==MINUS_EQUAL||op==MUL_EQUAL||op==DIV_EQUAL))
			{error("'=', '+=', '-=', '*=' or '/=' expected"); return;}
		if (op!='=' && (expressionType==Variable.STRING||expressionType==Variable.ARRAY))
			{error("'=' expected"); return;}
		Variable[] array = v.getArray();
		if (array==null)
			error("Array expected");
		if (index<0)
			error("Negative index");
		if (index>=array.length) {  // expand array
			if (!func.expandableArrays)
				error("Index ("+index+") out of range");
			Variable[] array2 = new Variable[index+array.length/2+1];
			boolean strings = array.length>0 && array[0].getString()!=null;
			for (int i=0; i<array2.length; i++) {
				if (i<array.length)
					array2[i] = array[i];
				else {
					array2[i] = new Variable(Double.NaN);
					if (strings)
						array2[i].setString("undefined");
				}
			}
			v.setArray(array2);
			v.setArraySize(index+1);
			array = v.getArray();
		}
		int size = v.getArraySize();
		if (index+1>size)
			v.setArraySize(index+1);
		int next = nextToken();
		switch (expressionType) {
			case Variable.STRING:
				array[index].setString(getString());
				break;
			case Variable.ARRAY:
				getToken();
				if (token==ARRAY_FUNCTION)
					array[index].setArray(func.getArrayFunction(pgm.table[tokenAddress].type));
				break;
			case USER_FUNCTION:
				int savePC = pc;
				getToken(); // the function
				boolean simpleFunctionCall = isSimpleFunctionCall(true);
				pc = savePC;
				if (simpleFunctionCall) {
					getToken(); // the function
					Variable v2 = runUserFunction();
					if (v2==null)
						error("No return value");
					if (done) return;
					int type = v2.getType();
					if (type==Variable.VALUE)
						array[index].setValue(v2.getValue());
					else
						array[index].setString(v2.getString());
				} else
					array[index].setValue(getExpression());
				break;
			default:
				switch (op) {
					case '=': array[index].setValue(getExpression()); break;
					case PLUS_EQUAL: array[index].setValue(array[index].getValue()+getExpression()); break;
					case MINUS_EQUAL: array[index].setValue(array[index].getValue()-getExpression()); break;
					case MUL_EQUAL: array[index].setValue(array[index].getValue()*getExpression()); break;
					case DIV_EQUAL: array[index].setValue(array[index].getValue()/getExpression()); break;
				}
				break;
		}				
	}

	final void doUserFunctionAssignment() {
		//IJ.log("doUserFunctionAssignment0: "+pgm.decodeToken(token, tokenAddress));
		putTokenBack();
		int savePC = pc;
		getToken(); // the variable
		getToken(); // '='
		getToken(); // the function
		boolean simpleAssignment = isSimpleFunctionCall(true);
		pc = savePC;
		if (!simpleAssignment)
			getAssignmentExpression();
		else {
			getToken();		
			Variable v1 = lookupLocalVariable(tokenAddress);
			if (v1==null)
				v1 = push(tokenAddress, 0.0, null, this);
			getToken();
			if (token!='=')
				error("'=' expected");
			getToken(); // the function
			Variable v2 = runUserFunction();
			if (v2==null)
				error("No return value");
			if (done) return;
			int type = v2.getType();
			if (type==Variable.VALUE)
				v1.setValue(v2.getValue());
			else if (type==Variable.ARRAY) {
				v1.setArray(v2.getArray());
				v1.setArraySize(v2.getArraySize());
			} else
				v1.setString(v2.getString());
		}	
	}
	
	boolean isSimpleFunctionCall(boolean assignment) {
		int count = 0;
		do {
			getToken();
			if (token=='(')
				count++;
			else if (token==')')
				count--;
			else if (done)
				error("')' expected");
		} while (count>0);
		getToken();
		if (assignment)
			return token==';';
		else
			return token==','||token==')';
	}
	
	final void doStringAssignment() {
		Variable v = lookupLocalVariable(tokenAddress);
		if (v==null) {
			if (nextToken()=='=')
				v = push(tokenAddress, 0.0, null, this);
			else
				error("Undefined identifier");
		}
		getToken();
		if (token=='=')
			v.setString(getString());
		else if (token==PLUS_EQUAL)
			v.setString(v.getString()+getString());
		else
			error("'=' or '+=' expected");
	}

	final void doArrayAssignment() {
		Variable v = lookupLocalVariable(tokenAddress);
		if (v==null) {
			if (nextToken()=='=')
				v = push(tokenAddress, 0.0, null, this);
			else
				error("Undefined identifier");
		}
		getToken();
		if (token!='=') {
			error("'=' expected");
			return;
		}
		getToken();
		if (token==ARRAY_FUNCTION)
			v.setArray(func.getArrayFunction(pgm.table[tokenAddress].type));
		else if (token==WORD) {
			Variable v2 = lookupVariable();
			v.setArray(v2.getArray());
			v.setArraySize(v2.getArraySize());
		} else if (token==VARIABLE_FUNCTION) {
			Variable v2 = func.getVariableFunction(pgm.table[tokenAddress].type);
			Variable[] array = v2.getArray();
			if (array==null)
				error("Array expected");			
			v.setArray(array);
		} else
			error("Array expected");
	}

	final void doIf() {
		looseSyntax = false;
		boolean b = getBoolean();
		if (b)
			doStatement();
		else
			skipStatement();
		int next = nextToken();
		if (next==';') {
			getToken();
			next = nextToken();
		}
		if (next==ELSE) {
			getToken();
			if (b)
				skipStatement();
			else
				doStatement();
		}
	}

	final boolean getBoolean() {
		getLeftParen();
		double value = getLogicalExpression();
		checkBoolean(value);
		getRightParen();
		return value==0.0?false:true;
	}

	final double getLogicalExpression() {
		double v1 = getBooleanExpression();
		int next = nextToken();
		if (!(next==LOGICAL_AND || next==LOGICAL_OR))
			return v1;
		checkBoolean(v1);
		getToken();
		int op = token;	
		double v2 = getLogicalExpression();
		checkBoolean(v2);
		if (op==LOGICAL_AND)
			return (int)v1 & (int)v2;
		else if (op==LOGICAL_OR)
			return (int)v1 | (int)v2;
		return v1;
	}

	final double getBooleanExpression() {
		double v1 = 0.0;
		String s1 = null;
		int next = pgm.code[pc+1];
		int tok = next&TOK_MASK;
		if (tok==STRING_CONSTANT || tok==STRING_FUNCTION || isString(pc+1)) {
			if (numericStringFunction(pc+1))
				v1 = getExpression();
			else
				s1 = getString();
		} else
			v1 = getExpression();
		next = nextToken();
		if (next>=EQ && next<=LTE) {
			getToken();
			int op = token;
			if (s1!=null)
				return compareStrings(s1, getString(), op);
			double v2 = getExpression();
			switch (op) {
				case EQ:
					v1 = v1==v2?1.0:0.0;
					break;
				case NEQ:
					v1 = v1!=v2?1.0:0.0;
					break;
				case GT:
					v1 = v1>v2?1.0:0.0;
					break;
				case GTE:
					v1 = v1>=v2?1.0:0.0;
					break;
				case LT:
					v1 = v1<v2?1.0:0.0;
					break;
				case LTE:
					v1 = v1<=v2?1.0:0.0;
					break;
			}
		} else if (s1!=null) {
			if (s1.equals("true"))
				v1 = 1.0;
			else if (s1.equals("false"))
				v1 = 0.0;
			else
				v1 = Tools.parseDouble(s1, Double.NaN);
		}
		return v1;
	}
	
	private boolean numericStringFunction(int loc) {
		if ((pgm.code[loc]&TOK_MASK)!=STRING_FUNCTION)
			return false;
		int address = pgm.code[loc]>>TOK_SHIFT;
		int type = pgm.table[address].type;
		if (type==VARIABLE_FUNCTION||type==DIALOG||type==FILE||type==STRING||type==EXT||type==LIST||type==IJ_CALL)
			return false;
		if (pgm.code[loc+1]=='.' && (pgm.code[loc+2]&0xff)!=STRING_FUNCTION)
			return true;
		if (pgm.code[loc+1]=='(' && pgm.code[loc+2]==')' && pgm.code[loc+3]=='.'  && (pgm.code[loc+4]&0xff)!=STRING_FUNCTION)
			return true;
		return false;
	}

	// Returns true if the token at the specified location is a string
	boolean isString(int pcLoc) {
		int tok = pgm.code[pcLoc];
		if ((tok&0xff)==VARIABLE_FUNCTION) {
			int address = tok>>TOK_SHIFT;
			int type = pgm.table[address].type;
			int token2 = pgm.code[pcLoc+2];
			String name = pgm.table[token2>>TOK_SHIFT].str;
			if (Functions.isStringFunction(name,type))
				return true; 
		}
		if ((tok&TOK_MASK)!=WORD)
			return false;
		Variable v = lookupVariable(tok>>TOK_SHIFT);
		if (v==null)
			return false;
		if (pgm.code[pcLoc+1]=='[') {
			Variable[] array = v.getArray();
			if (array!=null && array.length>0)
				return array[0].getType()==Variable.STRING;
		}
		int type = v.getType();
		if (type==Variable.STRING && (pgm.code[pcLoc+1]&0xff)=='.' && (pgm.code[pcLoc+2]&0xff)!=STRING_FUNCTION)
			return false;
		return type==Variable.STRING;
	}

	double compareStrings(String s1, String s2, int op) {
		int result;
		result = s1.compareToIgnoreCase(s2);
		double v1 = 0.0;
		switch (op) {
			case EQ:
				v1 = result==0?1.0:0.0;
				break;
			case NEQ:
				v1 = result!=0?1.0:0.0;
				break;
			case GT:
				v1 = result>0?1.0:0.0;
				break;
			case GTE:
				v1 = result>=0?1.0:0.0;
				break;
			case LT:
				v1 = result<0?1.0:0.0;
				break;
			case LTE:
				v1 = result<=0?1.0:0.0;
				break;
		}
		return v1;
	}

	final double getAssignmentExpression() {
		int tokPlus2 = pgm.code[pc+2];
		if ((pgm.code[pc+1]&0xff)==WORD && (tokPlus2=='='||tokPlus2==PLUS_EQUAL
		||tokPlus2==MINUS_EQUAL||tokPlus2==MUL_EQUAL||tokPlus2==DIV_EQUAL)) {
			getToken();
			Variable v = lookupLocalVariable(tokenAddress);
			int saveAddress = tokenAddress;
			getToken();
			double value = 0.0;
			if (token=='=')
				value = getAssignmentExpression();
			else {
				value = v!=null?v.getValue():0.0;
				switch (token) {
					case PLUS_EQUAL: value += getAssignmentExpression(); break;
					case MINUS_EQUAL: value -= getAssignmentExpression(); break;
					case MUL_EQUAL: value *= getAssignmentExpression(); break;
					case DIV_EQUAL: value /= getAssignmentExpression(); break;
				}
			}
			if (v==null)
				v = push(saveAddress, 0.0, null, this);
			v.setValue(value);
			return value;
		} else
			return getLogicalExpression();
	}

	final void checkBoolean(double value) {
		if (!(value==0.0 || value==1.0))
			error("Boolean expression expected: "+value);
	}

	void doVar() {
		getToken();
		while (token==WORD) {
			if (nextToken()=='=')
				doAssignment();
			else {
				Variable v = lookupVariable(tokenAddress);
				if (v==null)
					push(tokenAddress, 0.0, null, this);
			}
			getToken();
			if (token==',')
				getToken();
			else {
				putTokenBack();
				break;
			}
		}
	}
	
	final void getLeftParen() {
		getToken();
		if (token!='(')
			error("'(' expected");
	}

	final void getRightParen() {
		getToken();
		if (token!=')')
			error("')' expected");
	}

	final void getParens() {
		if (nextToken()=='(') {
			getLeftParen();
			getRightParen();
		}
	}

	final void getComma() {
		getToken();
		if (token!=',') {
			if (looseSyntax)
				putTokenBack();
			else
				error("',' expected");
		}
	}

	void error (String message) {
		errorMessage = message;
		if (ignoreErrors)
			return;
		errorCount++;
		boolean showMessage = !done;
		String[] variables = showMessage?getVariables():null;
		token = EOF;
		tokenString = "";
		IJ.showStatus("");
		IJ.showProgress(0, 0);
		batchMode = false;
		imageTable = imageActivations = null;
		WindowManager.setTempCurrentImage(null);
		wasError = true;
		if (!evaluating)
			instance = null;
		if (showMessage && message!=null) {
			String line = getErrorLine();
			done = true;
			if (line.length()>120)
				line = line.substring(0,119)+"...";			
			Frame f = WindowManager.getFrame("Debug");			
			TextPanel panel = null;
			if (showVariables && f!=null && (f instanceof TextWindow)) { //clear previous content
				TextWindow debugWindow = (TextWindow) f;
				if (debugWindow != null) {
					panel = debugWindow.getTextPanel();
					panel.clear();
				}	
			}
			String calledFrom = "";
			if (callDepth > 0 && callStack != null) {
				for (int jj = callDepth - 1; jj >= 0; jj--) {
					int theline = callStack[jj];
					calledFrom += "\t\t(called from line " + theline +")\n";
				}
			}
			showError("Macro Error", message+" in line "+lineNumber +"\n" + calledFrom +" \n"+line, variables);
			f = WindowManager.getFrame("Debug");
			if (showVariables && f!=null && (f instanceof TextWindow)) {
				TextWindow debugWindow = (TextWindow)f;
				debugWindow.append("\n---\t\t---\nError:\t\t" + message + " in line "+lineNumber + ":");
				debugWindow.append(calledFrom + "\t\t");	
				debugWindow.append("\t\t"+line);
			}			
			throw new RuntimeException(Macro.MACRO_CANCELED);
		}
		done = true;
		if (errorCount>10) 
			throw new RuntimeException(Macro.MACRO_CANCELED);
	}
		
	void showError(String title, String msg, String[] variables) {
		boolean noImages = msg.startsWith("There are no images open");
		if (noImages)
			title = "No Image";
		Macro.setOptions(null);
		GenericDialog gd = new GenericDialog(title);
		gd.setInsets(6,5,0);
		gd.addMessage(msg);
		gd.setInsets(15,30,5);
		if (!noImages)
			gd.addCheckbox("Show \"Debug\" Window", showVariables);
		gd.hideCancelButton();
		gd.showDialog();
		if (!noImages)
			showVariables = gd.getNextBoolean();
		else
			showVariables = false;
		if (!gd.wasCanceled() && showVariables)
			updateDebugWindow(variables, null);
	}

	public TextWindow updateDebugWindow(String[] variables, TextWindow debugWindow) {
		if (debugWindow==null) {
			Frame f = WindowManager.getFrame("Debug");
			if (f!=null && (f instanceof TextWindow)) {
				debugWindow = (TextWindow)f;
				debugWindow.toFront();
			}
		}
		if (debugWindow==null)
			debugWindow = new TextWindow("Debug", "Name\t*\tValue", "", 300, 400);
		TextPanel panel = debugWindow.getTextPanel();
		int n = variables.length;
		if (n==0) {
			panel.clear();
			return debugWindow;
		}
		int lines = panel.getLineCount();
		String[] markedVariables = markChanges(variables);
		for (int i=0; i<lines; i++) {
			if (i<n)
				panel.setLine(i, markedVariables[i]);
			else
				panel.setLine(i, "");
		}
		for (int i=lines; i<n; i++)
			debugWindow.append(markedVariables[i]);
		return debugWindow;
	}

	private static String[] prevVars; //previous variables for comparison

	private String[] markChanges(String[] newVars) {//add asterisk if variable has changed
		int len = newVars.length;
		String[] copyOfNew = new String[len];
		String[] hilitedVars = new String[len];
		for (int jj = 0; jj < len; jj++) {
			copyOfNew[jj] = newVars[jj];
			String marker = "\t*\t";//changed
			if (prevVars != null && jj < prevVars.length && jj < len && prevVars[jj].equals(newVars[jj]))
				marker = "\t\t";//unchanged
			hilitedVars[jj] = newVars[jj].replaceFirst("\t", marker);
		}
		prevVars = copyOfNew;
		return hilitedVars;
	}

	String getErrorLine() {
		int savePC = pc;
		lineNumber = pgm.lineNumbers[pc];
		while (pc>=0 && lineNumber==pgm.lineNumbers[pc])
			pc--;   //go to beginning of line
		if (lineNumber<=1)
			pc = -1;
		String line = "";
		getToken();
		while (!done && lineNumber==pgm.lineNumbers[pc]) {
			String str = pgm.decodeToken(token, tokenAddress);
			if (pc==savePC)
				str = "<" + str + ">";
			line += str + " ";
			getToken();
		}
		return line;
	}

	final String getString() {
		String str = getStringTerm();
		while (true) {
			getToken();
			if (token=='+')
				str += getStringTerm();
			else {
				putTokenBack();
				break;
			}
		}
		return str;
	}

	final String getStringTerm() {
		String str;
		Variable v;
		getToken();
		switch (token) {
			case STRING_CONSTANT:
				str = tokenString;
				break;
			case STRING_FUNCTION:
				str = func.getStringFunction(pgm.table[tokenAddress].type);
				break;
			case VARIABLE_FUNCTION:		
				if (!isString(pc)) {
					putTokenBack();
					str = toString(getStringExpression());
					break;
				}
				v = func.getVariableFunction(pgm.table[tokenAddress].type);
				str = v.getString();
				if (str==null) {
					double value = v.getValue();
					if ((int)value==value)
						str = IJ.d2s(value,0);
					else
						str = ""+value;
				}
				break;
			case USER_FUNCTION:
				v = runUserFunction();
				if (v==null)
					error("No return value");
				str = v.getString();
				if (str==null) {
					double value = v.getValue();
					if ((int)value==value)
						str = IJ.d2s(value,0);
					else
						str = ""+value;
				}
				break;
			case WORD:
				str = lookupStringVariable();
				if (str!=null)
					break;
				// else fall through
			default:
				putTokenBack();
				str = toString(getStringExpression());
		}
		return str;
	}
	
	private String toString(double x) {
		if ((int)x==x)
			return IJ.d2s(x,0);
		else {
			if (evaluating)
				return ""+x;
			String str = IJ.d2s(x, 4, 9);
			while(str.endsWith("0") && str.contains(".") && !str.contains("E"))
				str = str.substring(0, str.length()-1);
			if (str.endsWith("."))
				str = str.substring(0, str.length()-1);
			return str;
		}
	}

	final boolean isStringFunction() {
		Symbol symbol = pgm.table[tokenAddress];
		return symbol.type==D2S;
	}

	final double getExpression() {
		double value = getTerm();
		int next;
		while (true) {
			next = nextToken();
			if (next=='+') {
				getToken();
				value += getTerm();
			} else if (next=='-') {
				getToken();
				value -= getTerm();
			} else
				break;
		}
		return value;
	}

	final double getTerm() {
		double value = getFactor();
		boolean done = false;
		int next;
		while (!done) {
			next = nextToken();
			switch (next) {
				case '*': getToken(); value *= getFactor(); break;
				case '/': getToken(); value /= getFactor(); break;
				case '%': getToken(); value %= getFactor(); break;
				case '&': getToken(); value = (int)value&(int)getFactor(); break;
				case '|': getToken(); value = (int)value|(int)getFactor(); break;
				case '^': getToken(); value = (int)value^(int)getFactor(); break;
				case SHIFT_RIGHT: getToken(); value = (int)value>>(int)getFactor(); break;
				case SHIFT_LEFT: getToken(); value = (int)value<<(int)getFactor(); break;
				default: done = true; break;
			}
		}
		return value;
	}

	final double getFactor() {
		double value = 0.0;
		Variable v = null;
		getToken();
		switch (token) {
			case NUMBER:
				value = tokenValue;
				break;
			case STRING_CONSTANT:
				value = Tools.parseDouble(tokenString,Double.NaN);
				break;
			case NUMERIC_FUNCTION:
				value = func.getFunctionValue(pgm.table[tokenAddress].type);
				break;
			case STRING_FUNCTION:
				String str = func.getStringFunction(pgm.table[tokenAddress].type);
				if (nextToken()=='.') {
					getToken();  // '.'
					getToken();  // numericFunction
					value = getNumericStringFunction(str);
				} else
					value = Tools.parseDouble(str);
				if ("NaN".equals(str))
					value = Double.NaN;
				else if (Double.isNaN(value))
					error("Numeric value expected");
				break;				
			case VARIABLE_FUNCTION:
				v = func.getVariableFunction(pgm.table[tokenAddress].type);
				if (v==null)
					error("No return value");
				if (v.getString()!=null) {
						error("Numeric return value expected");
				} else
					value = v.getValue();
				break;
			case USER_FUNCTION:
				v = runUserFunction();
				if (v==null)
					error("No return value");
				if (done)
					value = 0;
				else {
					if (v.getString()!=null)
						error("Numeric return value expected");
					else
						value = v.getValue();
				}
				break;
			case TRUE: value = 1.0; break;
			case FALSE: value = 0.0; break;
			case PI: value = Math.PI; break;
			case NaN: value = Double.NaN; break;
			case WORD:
				v = lookupVariable();
				if (v==null)
					return 0.0;
				int next = nextToken();
				if (next=='[') {
					v = getArrayElement(v);
					value = v.getValue();
					next = nextToken();
					if (next=='.') {
						value = runNumericFunction(v);
						next = nextToken();
					}
				} else if (next=='.') {
					value = runNumericFunction(v);
					next = nextToken();
				} else {
					if (v.getArray()!=null) {
						getToken();
						error("'[' or '.' expected");
					}
					if (prefixValue!=0 && !checkingType) {
						v.setValue(v.getValue()+prefixValue);
						prefixValue = 0;
					}
					value = v.getValue();
				}
				if (!(next==PLUS_PLUS || next==MINUS_MINUS))
					break;
				getToken();
				if (token==PLUS_PLUS)
					v.setValue(v.getValue()+(checkingType?0:1));
				else
					v.setValue(v.getValue()-(checkingType?0:1));
				break;
			case (int)'(':
				value = getLogicalExpression();
				getRightParen();
				break;
			case PLUS_PLUS:
				prefixValue = 1;
				value = getFactor();
				break;
			case MINUS_MINUS:
				prefixValue = -1;
				value = getFactor();
				break;
			case '!':
				value = getFactor();
				if (value==0.0 || value==1.0) {
					value = value==0.0?1.0:0.0;
				} else
					error("Boolean expected");
				break;
			case '-':
				value = -getFactor();
				break;
			case '~':
				value = ~(int)getFactor();
				break;
			default:
				error("Number or numeric function expected");
		}
		// IJ.log("getFactor: "+value+" "+pgm.decodeToken(preToken,0));
		return value;
	}


	private double getNumericStringFunction(String str) {
		double value = Double.NaN;
		if (token==WORD) {
			if (tokenString.equals("length")) {
				getParens();
				value = str.length();
			} else if (tokenString.equals("contains"))
				value = str.contains(func.getStringArg())?1:0;
			else if (tokenString.equals("charAt"))
				value = str.charAt((int)func.getArg());
		} else if (token==NUMERIC_FUNCTION) {
			int type = pgm.table[tokenAddress].type;
			switch (type) {
				case INDEX_OF: value = func.indexOf(str); break;
				case LAST_INDEX_OF: value = str.lastIndexOf(func.getStringArg()); break;
				case STARTS_WITH: value = str.startsWith(func.getStringArg())?1:0; break;
				case ENDS_WITH: value = str.endsWith(func.getStringArg())?1:0; break;
				case MATCHES: value = func.matches(str); break;
			}
		} else
			error("Numeric function expected");
		return value;
	}

	final Variable getArrayElement(Variable v) {
		int index = getIndex();
		Variable[] array = v.getArray();
		if (array==null)
			error("Array expected");
		if (index<0 || index>=array.length) {
			if (array.length==0)
				error("Empty array");
			else
				error("Index ("+index+") out of 0-"+(array.length-1)+" range");
		}
		return array[index];
	}
	
	final double runNumericFunction(Variable v) {
		getToken(); // '.'
		getToken();
		if (token==WORD && v.getArray()!=null && tokenString.equals("length"))
			return v.getArraySize();			
		String str = v.getString();
		if (str==null)
			error("Array or string expected");	
		return getNumericStringFunction(str);
	}
	
	final double getStringExpression() {
		double value = getTerm();
		while (true) {
			getToken();
			if (token=='+') {
				getToken();
				if (token==STRING_CONSTANT || token==STRING_FUNCTION) {
					putTokenBack();
					putTokenBack();
					break;
				}
				if (token==WORD) {
					Variable v = lookupVariable(tokenAddress);
					if (v!=null && v.getString()!=null) {
						putTokenBack();
						putTokenBack();
						break;
					}
				}
				putTokenBack();
				value += getTerm();
			} else if (token=='-')
				value -= getTerm();
			else {
				putTokenBack();
				break;
			}
		};
		return value;
	}

	/** Searches the local and global sections of the stack for.
		the specified variable. Returns null if it is not found. */
	final Variable lookupLocalVariable(int symTabAddress) {
		//IJ.log("lookupLocalVariable: "+topOfStack+" "+startOfLocals+" "+topOfGlobals);
		Variable v = null;
		for (int i=topOfStack; i>=startOfLocals; i--) {
			if (stack[i].symTabIndex==symTabAddress) {
				v = stack[i];
				break;
			}
		}
		if (v==null) {
			for (int i=topOfGlobals; i>=0; i--) {
				if (stack[i].symTabIndex==symTabAddress) {
					v = stack[i];
					break;
				}
			}
		}
		return v;
	}

	/** Searches the entire stack for the specified variable. Returns null if it is not found. */
	final Variable lookupVariable(int symTabAddress) {
		Variable v = null;
		for (int i=topOfStack; i>=0; i--) {
			if (stack[i].symTabIndex==symTabAddress) {
				v = stack[i];
				break;
			}
		}
		return v;
	}

	Variable push(Variable var, Interpreter interp) {
		if (stack==null)
			stack = new Variable[STACK_SIZE];
		if (topOfStack>=(STACK_SIZE-2))
			interp.error("Stack overflow");
		else
			topOfStack++;
		stack[topOfStack] = var;
		return var;
	}

	void pushGlobals() {
		if (pgm.globals==null)
			return;
		if (stack==null)
			stack = new Variable[STACK_SIZE];
		for (int i=0; i<pgm.globals.length; i++) {
			topOfStack++;
			stack[topOfStack] = pgm.globals[i];
		}
		topOfGlobals = topOfStack;
	}

	/** Creates a Variable and pushes it onto the stack. */
	Variable push(int symTabLoc, double value, String str, Interpreter interp) {
		Variable var = new Variable(symTabLoc, value, str);
		if (stack==null)
			stack = new Variable[STACK_SIZE];
		if (topOfStack>=(STACK_SIZE-2))
			interp.error("Stack overflow");
		else
			topOfStack++;
		stack[topOfStack] = var;
		return var;
	}

	void trimStack(int previousTOS, int previousStartOfLocals) {
		for (int i=previousTOS+1; i<=topOfStack; i++)
			stack[i] = null;
		topOfStack = previousTOS;
	    startOfLocals = previousStartOfLocals;
	}
	
	/** Searches the entire stack for the variable associated with the 
		current token. Aborts the macro if it is not found. */
	final Variable lookupVariable() {
		Variable v = null;
		if (stack==null) {
			undefined();
			return v;
		}
		boolean found = false;
		for (int i=topOfStack; i>=0; i--) {
			v = stack[i];
			if (v.symTabIndex==tokenAddress) {
				found = true;
				break;
			}
		}
		if (!found)
			undefined();
		return v;
	}

	final String lookupStringVariable() {
		if (stack==null) {
			undefined();
			return "";
		}
		boolean found = false;
		String str = null;
		for (int i=topOfStack; i>=0; i--) {
			if (stack[i].symTabIndex==tokenAddress) {
				Variable v = stack[i];
				found = true;
				int next = nextToken();
				if (next=='[') {
					int savePC = pc;
					int index = getIndex();
					Variable[] array = v.getArray();
					if (array==null)
						error("Array expected");
					if (index<0 || index>=array.length)
						error("Index ("+index+") out of 0-"+(array.length-1)+" range");
					str = array[index].getString();
					int next2 = nextToken();			
					if (str!=null) {
						if (next2=='.')
							str = runStringFunction(str);
					} else {
						if (next2==')' || next2==';')
							str = toString(array[index].getValue());
						else {
							pc = savePC-1;
							getToken();
						}
					}
				} else if (next=='.') {
					if (v.getString()!=null)
						str = runStringFunction(v.getString());
				} else {
					if (v.getArray()!=null)
						{getToken(); error("'[' or '.' expected");}
					str = v.getString();
				}
				break;
			}
		}
		if (!found)
			undefined();
		return str;
	}
	
	private String runStringFunction(String str) {
		getToken(); // '.'
		getToken();
		if (token==WORD) {
			if (tokenString.equals("length")) {
				getParens();
				str = ""+str.length();
			} else if (tokenString.equals("contains")) {
				str = ""+str.contains(func.getStringArg());
			} else if (tokenString.equals("replaceAll")) {
				str = func.replace(str);
			} else
				str = null;
		} else if (token==NUMERIC_FUNCTION) {
			int type = pgm.table[tokenAddress].type;
			switch (type) {
				case INDEX_OF: str = ""+func.indexOf(str); break;
				case LAST_INDEX_OF: str = ""+str.lastIndexOf(func.getStringArg()); break;
				case STARTS_WITH: str = ""+str.startsWith(func.getStringArg()); break;
				case ENDS_WITH: str = ""+str.endsWith(func.getStringArg()); break;
				case MATCHES: str = ""+func.matches(str); break;
				default:
					str = null;
			}
		} else if (token==STRING_FUNCTION) {
			int type = pgm.table[tokenAddress].type;
			switch (type) {
				case SUBSTRING: str = func.substring(str); break;
				case TO_LOWER_CASE: getParens(); str = str.toLowerCase(Locale.US); break;
				case TO_UPPER_CASE: getParens(); str = str.toUpperCase(Locale.US); break;
				case REPLACE: str = func.replace(str); break;
				case TRIM: getParens();  str = str.trim(); break;
				case CHARAT:
					int index = (int)func.getArg();
					func.checkIndex(index, 0, str.length()-1);
					str = ""+str.charAt(index);
					break;
				default:
					str = null;
			}
		} else
			str = null;
		if (str==null)
			error("String function expected");
		return str;
	}

	int getIndex() {
		getToken();
		if (token!='[')
			error("'['expected");
		int index = (int)getExpression();
		getToken();
		if (token!=']')
			error("']' expected");
		return index;
	}
	
	void undefined() {
		if (nextToken()=='(')
			error("Undefined identifier");
		else {
			if (pgm.getSize()==1) {
				String cmd = pgm.decodeToken(pgm.code[0]);
				cmd = cmd.replaceAll("_", " ");
				Hashtable commands = Menus.getCommands();
				if (commands!=null && commands.get(cmd)!=null)
					IJ.run(cmd);
				else
					error("Undefined variable");
			} else
				error("Undefined variable");
		}
	}
	
	void dump() {
		getParens();
		if (!done) {
			pgm.dumpSymbolTable();
			pgm.dumpProgram();
			dumpStack();
		}
	}

	void dumpStack() {
		IJ.log("");
		IJ.log("Stack");
		if (stack!=null) {
			for (int i=topOfStack; i>=0; i--) {
				Variable v = stack[i];
				Symbol symbol = v!=null?pgm.table[v.symTabIndex]:null;
				IJ.log(i+" "+(symbol!=null?symbol.str:"null")+" "+v);
			}
		}
	}
	
	void finishUp() {
		if (batchMacro)
			batchMacroImage = WindowManager.getCurrentImage();
		func.updateDisplay();
		instance = null;
		if (!calledMacro || batchMacro) {
			if (batchMode)
				showingProgress = true;
			batchMode = false;
			imageTable = imageActivations = null;
			WindowManager.setTempCurrentImage(null);
		}
		if (func.plot!=null) {
			func.plot.show();
			func.plot = null;
		}
		if (showingProgress)
			IJ.showProgress(0, 0);
		if (keysSet) {
			IJ.setKeyUp(KeyEvent.VK_ALT);
			IJ.setKeyUp(KeyEvent.VK_SHIFT);		
			IJ.setKeyUp(KeyEvent.VK_SPACE);
		}
		if (rgbWeights!=null)
			ColorProcessor.setWeightingFactors(rgbWeights[0], rgbWeights[1], rgbWeights[2]);
		if (func.writer!=null)
			func.writer.close();
		func.roiManager = null;
		if (func.resultsPending) {
			ResultsTable rt = ResultsTable.getResultsTable();
			if (rt!=null && rt.size()>0)
				rt.show("Results");
		}
		if (func.unUpdatedTable!=null)
			func.unUpdatedTable.show(func.unUpdatedTable.getTitle());
	}
	
	/** Aborts currently running macro. */
	public static void abort() {
		//IJ.log("abort: "+(instance!=null?""+instance.hashCode():"null"));
		if (instance!=null)
			instance.abortMacro();
	}
	
	/** Aborts the macro that was running when this one started. */
	static void abortPrevious() {
		if (previousInstance!=null) {
			previousInstance.abortMacro();
			IJ.beep();
			previousInstance = null;
		}
	}

	/** Absolete, replaced by abortMacro(). */
	public static void abort(Interpreter interp) {
		if (interp!=null)
			interp.abortMacro();
	}
	
	/** Aborts this macro. */
	public void abortMacro() {
		if (!calledMacro || batchMacro) {
			batchMode = false;
			imageTable = imageActivations = null;
		}
		if (func!=null && !(macroName!=null&&macroName.indexOf(" Tool")!=-1))
			func.abortDialog();
		IJ.showStatus("Macro aborted");
		shutdown();
		//IJ.log("abortMacro1: "+done+" "+(instance!=null?""+instance.hashCode():"null"));
		long t0 = System.currentTimeMillis();
		while ((System.currentTimeMillis()-t0)<2000 && instance!=null)
			IJ.wait(5);
		if (instance!=null) {
			abortAllMacroThreads();
			setInstance(null);
		}
	}
	
	public void abort(String message) {
		errorMessage = message;
		if (ignoreErrors) {
			done = true;
			finishUp();
		} else {
			error(message);
		}
	}
	
	private synchronized void shutdown() {
		ignoreErrors = true;
		for (int i=0; i<10; i++)
			done = true;
	}
		
	private void abortAllMacroThreads() {
		try {
			ThreadGroup group = Thread.currentThread().getThreadGroup(); 
			int activeCount = group.activeCount(); 
			Thread[] threads = new Thread[activeCount]; 
			group.enumerate(threads); 
			for (int i = 0; i < activeCount; i++) { 
				String name = threads[i].getName(); 
				if (name!=null && name.endsWith("Macro$"))
					threads[i].stop(); 
			}
		} catch (Throwable e) {
		}
	} 

	public static Interpreter getInstance() {
		return instance;
	}
	
	static void setInstance(Interpreter i) {
		instance = i;
	}

	static void setBatchMode(boolean b) {
		batchMode = b;
		if (b==false)
			imageTable = imageActivations = null;
	}

	public static boolean isBatchMode() {
		return batchMode && !tempShowMode;
	}
	
	public static void addBatchModeImage(ImagePlus imp) {
		if (!batchMode || imp==null) return;
		if (imageTable==null)
			imageTable = new Vector();
		imageTable.add(imp);
		activateImage(imp);
	}

	public static void removeBatchModeImage(ImagePlus imp) {
		if (imageTable!=null && imp!=null) {
			int index = imageTable.indexOf(imp);
			if (index!=-1) {
				imageTable.remove(index);
				imageActivations.remove(imp);
				WindowManager.setTempCurrentImage(getLastBatchModeImage());
			}
		}
	}
	
	public static void activateImage(ImagePlus imp) {
		if (imageTable!=null && imp!=null) {
			if (imageActivations==null)
				imageActivations = new Vector();
			imageActivations.remove(imp);
			imageActivations.add(imp);
		}
	}

	public static int[] getBatchModeImageIDs() {
		if (!batchMode || imageTable==null)
			return new int[0];
		int n = imageTable.size();
		int[] imageIDs = new int[n];
		for (int i=0; i<n; i++) {
			ImagePlus imp = (ImagePlus)imageTable.get(i);
			imageIDs[i] = imp.getID();
		}
		return imageIDs;
	}

	public static int getBatchModeImageCount() {
		if (!batchMode || imageTable==null)
			return 0;
		else
			return imageTable.size();
	}
	
	public static ImagePlus getBatchModeImage(int id) {
		if (!batchMode || imageTable==null)
			return null;
		for (Enumeration en=Interpreter.imageTable.elements(); en.hasMoreElements();) {
			ImagePlus imp = (ImagePlus)en.nextElement();
			if (id==imp.getID())
				return imp;
		}
		return null;
	}
	
	public static ImagePlus getLastBatchModeImage() { 
		if (!batchMode || imageTable==null)
			return null; 
		ImagePlus imp2 = null;
		try {
			int size = imageTable.size(); 
			if (size==0)
				return null;
			if (imageActivations!=null && imageActivations.size()>0)
				imp2 =  (ImagePlus)imageActivations.get(imageActivations.size()-1);
			if (imp2==null)
				imp2 = (ImagePlus)imageTable.get(size-1);
		} catch(Exception e) { }
		return imp2;
	} 
 
 	/** Obsolete; replaced by the #include statement. */
 	public static void setAdditionalFunctions(String functions) {
 		additionalFunctions = functions;
	} 

 	public static String getAdditionalFunctions() {
 		return additionalFunctions;
	} 
	
	/** Returns the batch mode RoiManager instance. */
	public static RoiManager getBatchModeRoiManager() {
		Interpreter interp = getInstance();
		if (interp!=null && isBatchMode() && RoiManager.getRawInstance()==null) {
			if (interp.func.roiManager==null)
				interp.func.roiManager = new RoiManager(true);
			return interp.func.roiManager;
		} else
			return null;
	}
	
	/** Returns true if there is an internal batch mode RoiManager. */
	public static boolean isBatchModeRoiManager() {
		Interpreter interp = getInstance();
		return interp!=null && isBatchMode() && interp.func.roiManager!=null;
	}
	
	public void setDebugger(Debugger debugger) {
		this.debugger = debugger;
		if (debugger!=null)
			debugMode = Debugger.STEP;
		else
			debugMode = Debugger.NOT_DEBUGGING;
	}
	
	// Returns the Debugger (editor), if any, associated with this macro. */
	public Debugger getDebugger() {
		return debugger;
	}

	public void setDebugMode(int mode) {
		debugMode = mode;
	}
	
	public int getLineNumber() {
        return pgm.lineNumbers[pc];
    }

	/** Returns the names of all variables and functions with human-readable annotations */
	public String[] getVariables() {
		int nImages = WindowManager.getImageCount();
		if (nImages>0) showDebugFunctions = true;
		int nFunctions = showDebugFunctions?3:0;
		String[] variables = new String[topOfStack+1+nFunctions];
		if (showDebugFunctions) {
			String title = null;
			if (nImages>0) {
				ImagePlus imp = WindowManager.getCurrentImage();
				if (imp!=null) title = imp.getTitle();
			}
			if (debugMode==Debugger.STEP) System.gc();
			variables[0] = "Memory\t" + IJ.freeMemory();
			variables[1] = "nImages()\t" + nImages;
			variables[2] = "getTitle()\t" + (title!=null?"\""+title+"\"":"");
		}
		String name;
		int index = nFunctions;
		for (int i=0; i<=topOfStack; i++) {
			name = pgm.table[stack[i].symTabIndex].str;
			if (i<=topOfGlobals)
				name += " (g)";
			variables[index++] = name + "\t" + stack[i];
		}
		return variables;
	}
	
	/** Returns the names of all variables, without any annotation */
	public String[] getVariableNames() {
		String[] variables = new String[topOfStack+1];
		for (int i=0; i<=topOfStack; i++)
			variables[i] = pgm.table[stack[i].symTabIndex].str;
		return variables;
	}

	// Returns 'true' if this macro has finished or if it was aborted. */
	public boolean done() {
		return done;
	}

	// Returns 'true' if this macro generated an error and was aborted. */
	public boolean wasError() {
		return wasError;
	}

	public void setVariable(String name, double value) {
		int index;
		for (int i=0; i<=topOfStack; i++) {
			index = stack[i].symTabIndex;
			if (pgm.table[index].str.equals(name)) {
				stack[i].setValue(value);
				break;
			}
		}
	}
	
	public void setVariable(String name, String str) {
		int index;
		for (int i=0; i<=topOfStack; i++) {
			index = stack[i].symTabIndex;
			if (pgm.table[index].str.equals(name)) {
				stack[i].setString(str);
				break;
			}
		}
	}

	public double getVariable(String name) {
		int index;
		for (int i=0; i<=topOfStack; i++) {
			index = stack[i].symTabIndex;
			if (pgm.table[index].str.equals(name))
				return stack[i].getValue();
		}
		return Double.NaN;
	}

	public double getVariable2(String name) {
		int index;
		for (int i=topOfStack; i>=0; i--) {
			index = stack[i].symTabIndex;
			if (pgm.table[index].str.equals(name))
				return stack[i].getValue();
		}
		return Double.NaN;
	}

	public String getStringVariable(String name) {
		int index;
		for (int i=topOfStack; i>=0; i--) {
			index = stack[i].symTabIndex;
			if (pgm.table[index].str.equals(name))
				return stack[i].getString();
		}
		return null;
	}
	
	public String getVariableAsString(String name) {
		String s = getStringVariable(name);
		if (s==null) {
			double value = getVariable2(name);
			if (!Double.isNaN(value)) s=""+value;
		}
		return s;
	}
	
	 /**
	 * Shows array elements after clicking an array variable in Debug
	 * window
	 * N. Vischer 
	 *
	 * @param row Debug window row of variable to be shown
	 */
	public void showArrayInspector(int row) {
		if (stack==null)
			return;
		int nFunctions = showDebugFunctions?3:0;
		int stkPos = row - nFunctions;
		if (stack.length>stkPos && stkPos>=0) {
			Variable var = stack[stkPos];
			if (var==null)
				return;
			if (var.getType()!=Variable.ARRAY && arrayWindow!=null)
				arrayWindow.setVisible(false);
			if (var.getType()==Variable.ARRAY) {
				String headings = "Index\t*\tValue";
				if (arrayWindow==null)
					arrayWindow = new TextWindow("Array", "", "", 170, 300);
				arrayWindow.setVisible(true);
 				int symIndex = var.symTabIndex;
				String arrName = pgm.table[symIndex].str;
				inspectStkIndex = stkPos;
				inspectSymIndex = symIndex;
				TextPanel txtPanel = arrayWindow.getTextPanel();
				String oldText = txtPanel.getText();// possible NullPointer at ij.text.TextPanel.getText(TextPanel.java:875) vData == null
				String[] oldLines = oldText.split("\n");
				txtPanel.clear();
				txtPanel.setColumnHeadings(headings);
				Variable[] elements = var.getArray();
				String title = arrName + "[" + elements.length + "]";
				arrayWindow.setTitle(title);
				arrayWindow.rename(title);
				String newText = "";
				String valueStr = "";
				for (int jj=0; jj<elements.length; jj++) {
					Variable element = elements[jj];
					if (element.getType()==Variable.STRING) {
						valueStr = elements[jj].getString();
						valueStr = valueStr.replaceAll("\n", "\\\\n");
						valueStr = "\"" + valueStr + "\""; //show it's a string
					} else if (element.getType()==Variable.VALUE) {
						double v = elements[jj].getValue();
						if ((int)v==v)
							valueStr = IJ.d2s(v, 0);
						else
							valueStr = ResultsTable.d2s(v, 4);
					}
						String flag = " ";
						if (oldLines.length > jj + 1){
							String [] parts = oldLines[jj+1].split("\t");
							String oldValue = parts[2];
							if (!valueStr.equals(oldValue))
									flag ="*";
						}
					String ss = ("" + jj + "\t" + flag +"\t" + valueStr) + "\n";
					newText += ss;
				}
				txtPanel.append(newText);
				txtPanel.scrollToTop();
				if (debugger!=null && (debugger instanceof Window))
					((Window)debugger).toFront();
				// scroll position should not change during single-stepping
			}
		}
	}

	/**
	 * Updates Array inspector if variable exists, otherwise closes
	 * ArrayInspector
	 */
	public void updateArrayInspector() {
		boolean varExists = false;
		if (arrayWindow!=null && arrayWindow.isVisible()) {
 			for (int stkIndex=0; stkIndex<=topOfStack; stkIndex++) {
				Variable var = stack[stkIndex];
				int symIndex = var.symTabIndex;
				if (inspectStkIndex==stkIndex && inspectSymIndex==symIndex && var.getType()==Variable.ARRAY) {
					varExists = true;
					break;
				}
			}
			if (varExists)
				showArrayInspector(inspectStkIndex+(showDebugFunctions?3:0));
			else{
				arrayWindow.setVisible(false);
				arrayWindow.getTextPanel().clear();
			}
		}
	}
	
	static void setTempShowMode(boolean mode) {
		tempShowMode = mode;
	}
	
	private static Interpreter lastInterp;
		
	public void setApplyMacroTable(ResultsTable rt) {
		applyMacroTable = rt;
	}
	
	public void setIgnoreErrors(boolean ignoreErrors) {
		this.ignoreErrors = ignoreErrors;
	}
	
	public String getErrorMessage() {
		return errorMessage;
	}
			
} // class Interpreter





