package ij.macro;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.measure.*;
import ij.plugin.*;
import ij.plugin.filter.*;
import ij.plugin.frame.*;
import ij.text.*;
import ij.io.*;
import ij.util.*;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import java.awt.event.KeyEvent;
import java.lang.reflect.*;
import java.net.URL;
import java.awt.datatransfer.*;

/** This class implements the built-in macro functions. */
public class Functions implements MacroConstants, Measurements {
    Interpreter interp;
    Program pgm;
    boolean updateNeeded;
    boolean autoUpdate = true;
    ImagePlus defaultImp;
    ImageProcessor defaultIP;
    int imageType;
    boolean colorSet, fontSet;
    Color defaultColor;
    double defaultValue = Double.NaN;
    Plot plot;
    static int plotID;
    int justification = ImageProcessor.LEFT_JUSTIFY;
    Font font;
    GenericDialog gd;
    PrintWriter writer;
    boolean altKeyDown, shiftKeyDown;
    boolean antialiasedText;
    StringBuffer buffer;
    RoiManager roiManager;
    Properties props;
    
    boolean saveSettingsCalled;
    boolean usePointerCursor, hideProcessStackDialog;
    float divideByZeroValue;
    int jpegQuality;
    int lineWidth;
    boolean doScaling;
    boolean weightedColor;
    double[] weights;
    boolean interpolateScaledImages, open100Percent, blackCanvas;
    boolean useJFileChooser,debugMode;
    Color foregroundColor, backgroundColor, roiColor;
    boolean pointAutoMeasure, requireControlKey, useInvertingLut;
    boolean doubleBuffer, disablePopup;
    int measurements;
    int decimalPlaces;
    boolean blackBackground;
    static Dialog waitForUserDialog;

    Functions(Interpreter interp, Program pgm) {
        this.interp = interp;
        this.pgm = pgm;
    }
 
    void doFunction(int type) {
        switch (type) {
            case RUN: doRun(); break;
            case SELECT: IJ.selectWindow(getStringArg()); resetImage(); break;
            case WAIT: IJ.wait((int)getArg()); break;
            case BEEP: interp.getParens(); IJ.beep(); break;
            case RESET_MIN_MAX: interp.getParens(); IJ.resetMinAndMax(); resetImage(); break;
            case RESET_THRESHOLD: interp.getParens(); IJ.resetThreshold(); resetImage(); break;
            case PRINT: print(); break;
            case WRITE: IJ.write(getStringArg()); break;
            case DO_WAND: IJ.doWand((int)getFirstArg(), (int)getLastArg()); resetImage(); break;
            case SET_MIN_MAX: IJ.setMinAndMax(getFirstArg(), getLastArg()); resetImage(); break;
            case SET_THRESHOLD: setThreshold(); break;
            case SET_TOOL: setTool(); break;
            case SET_FOREGROUND: setForegroundColor(); break;
            case SET_BACKGROUND: setBackgroundColor(); break;
            case SET_COLOR: setColor(); break;
            case MAKE_LINE: makeLine(); break;
            case MAKE_OVAL: makeOval(); break;
            case MAKE_RECTANGLE: makeRectangle(); break;
            case DUMP: interp.dump(); break;
            case LINE_TO: lineTo(); break;
            case MOVE_TO: moveTo(); break;
            case DRAW_LINE: drawLine(); break;
            case REQUIRES: requires(); break;
            case AUTO_UPDATE: autoUpdate = getBooleanArg(); break;
            case UPDATE_DISPLAY: interp.getParens(); updateDisplay(); break;
            case DRAW_STRING: drawString(); break;
            case SET_PASTE_MODE: IJ.setPasteMode(getStringArg()); break;
            case DO_COMMAND: doCommand(); break;
            case SHOW_STATUS: IJ.showStatus(getStringArg()); interp.statusUpdated=true; break;
            case SHOW_PROGRESS: showProgress(); break;
            case SHOW_MESSAGE: showMessage(false); break;
            case SHOW_MESSAGE_WITH_CANCEL: showMessage(true); break;
            case SET_PIXEL: case PUT_PIXEL: setPixel(); break;
            case SNAPSHOT: case RESET: case FILL: doIPMethod(type); break;
            case SET_LINE_WIDTH: getProcessor().setLineWidth((int)getArg()); break;
            case CHANGE_VALUES: changeValues(); break;
            case SELECT_IMAGE: selectImage(); break;
            case EXIT: exit(); break;
            case SET_LOCATION: setLocation(); break;
            case GET_CURSOR_LOC: getCursorLoc(); break;
            case GET_LINE: getLine(); break;
            case GET_VOXEL_SIZE: getVoxelSize(); break;
            case GET_HISTOGRAM: getHistogram(); break;
            case GET_BOUNDING_RECT: case GET_BOUNDS: getBounds(); break;
            case GET_LUT: getLut(); break;
            case SET_LUT: setLut(); break;
            case GET_COORDINATES: getCoordinates(); break;
            case MAKE_SELECTION: makeSelection(); break;
            case SET_RESULT: setResult(); break;
            case UPDATE_RESULTS: updateResults(); break;
            case SET_BATCH_MODE: setBatchMode(); break;
            case PLOT: doPlot(); break;
            case SET_JUSTIFICATION: setJustification(); break;
            case SET_Z_COORDINATE: setZCoordinate(); break;
            case GET_THRESHOLD: getThreshold(); break;
            case GET_PIXEL_SIZE: getPixelSize(); break;
            case SETUP_UNDO: interp.getParens(); Undo.setup(Undo.TRANSFORM, getImage()); break;
            case SAVE_SETTINGS: saveSettings(); break;
            case RESTORE_SETTINGS: restoreSettings(); break;
            case SET_KEY_DOWN: setKeyDown(); break;
            case OPEN: open(); break;
            case SET_FONT: setFont(); break;
            case GET_MIN_AND_MAX: getMinAndMax(); break;
            case CLOSE: close(); break;
            case SET_SLICE: setSlice(); break;
            case NEW_IMAGE: newImage(); break;
            case SAVE: IJ.save(getStringArg()); break;
            case SAVE_AS: saveAs(); break;
            case SET_AUTO_THRESHOLD: setAutoThreshold(); break;
            case RENAME: resetImage(); getImage().setTitle(getStringArg()); break;
            case GET_STATISTICS: getStatistics(true); break;
            case GET_RAW_STATISTICS: getStatistics(false); break;
            case FLOOD_FILL: floodFill(); break;
            case RESTORE_PREVIOUS_TOOL: restorePreviousTool(); break;
            case SET_VOXEL_SIZE: setVoxelSize(); break;
            case GET_LOCATION_AND_SIZE: getLocationAndSize(); break;
            case GET_DATE_AND_TIME: getDateAndTime(); break;
            case SET_METADATA: setMetadata(); break;
            case CALCULATOR: imageCalculator(); break;
            case SET_RGB_WEIGHTS: setRGBWeights(); break;
            case MAKE_POLYGON: makePolygon(); break;
            case SET_SELECTION_NAME: setSelectionName(); break;
            case DRAW_RECT: case FILL_RECT: case DRAW_OVAL: case FILL_OVAL: drawOrFill(type); break;
            case SET_OPTION: setOption(); break;
            case SHOW_TEXT: showText(); break;
            case SET_SELECTION_LOC: setSelectionLocation(); break;
            case GET_DIMENSIONS: getDimensions(); break;
            case WAIT_FOR_USER: waitForUser(); break;
            case MAKE_POINT: makePoint(); break;
        }
    }
    
    final double getFunctionValue(int type) {
        double value = 0.0;
        switch (type) {
            case GET_PIXEL: value = getPixel(); break;
            case ABS: case COS: case EXP: case FLOOR: case LOG: case ROUND: 
            case SIN: case SQRT: case TAN: case ATAN: case ASIN: case ACOS:
                value = math(type);
                break;
            case MAX_OF: case MIN_OF: case POW: case ATAN2: value=math2(type); break;
            case GET_TIME: interp.getParens(); value=System.currentTimeMillis(); break;
            case GET_WIDTH: interp.getParens(); value=getImage().getWidth(); break;
            case GET_HEIGHT: interp.getParens(); value=getImage().getHeight(); break;
            case RANDOM: value=random(); break;
            case GET_COUNT: case NRESULTS: value=getResultsCount(); break;
            case GET_RESULT: value=getResult(); break;
            case GET_NUMBER: value=getNumber(); break;
            case NIMAGES: value=getImageCount(); break;
            case NSLICES: value=getStackSize(); break;
            case LENGTH_OF: value=lengthOf(); break;
            case GET_ID: interp.getParens(); resetImage(); value=getImage().getID(); break;
            case BIT_DEPTH: interp.getParens(); value = getImage().getBitDepth(); break;
            case SELECTION_TYPE: value=getSelectionType(); break;
            case IS_OPEN: value=isOpen(); break;
            case IS_ACTIVE: value=isActive(); break;
            case INDEX_OF: value=indexOf(); break;
            case LAST_INDEX_OF: value=getFirstString().lastIndexOf(getLastString()); break;
            case CHAR_CODE_AT: value=getFirstString().charAt((int)getLastArg()); break;
            case GET_BOOLEAN: value=getBoolean(); break;
            case STARTS_WITH: case ENDS_WITH: value = startsWithEndsWith(type); break;
            case IS_NAN: value = Double.isNaN(getArg())?1:0; break;
            case GET_ZOOM: value = getZoom(); break;
            case PARSE_FLOAT: value = parseDouble(getStringArg()); break;
            case PARSE_INT: value = parseInt(); break;
            case IS_KEY_DOWN: value=isKeyDown(); break;
            case GET_SLICE_NUMBER: interp.getParens(); value=getImage().getCurrentSlice(); break;
            case SCREEN_WIDTH: case SCREEN_HEIGHT: value = getScreenDimension(type); break;
            case CALIBRATE: value = getImage().getCalibration().getCValue(getArg()); break;
            case ROI_MANAGER: value = roiManager(); break;
            case TOOL_ID: interp.getParens(); value = Toolbar.getToolId(); break;
            case IS: value = is(); break;
            case GET_VALUE: value = getValue(); break;
            case STACK: value = doStack(); break;
            case MATCHES: value = matches(); break;
            case GET_STRING_WIDTH: value = getStringWidth(); break;
            default:
                interp.error("Numeric function expected");
        }
        return value;
    }

    String getStringFunction(int type) {
        String str;
        switch (type) {
            case D2S: str = d2s(); break;
            case TO_HEX: str = toString(16); break;
            case TO_BINARY: str = toString(2); break;
            case GET_TITLE: interp.getParens(); resetImage(); str=getImage().getTitle(); break;
            case GET_STRING: str = getStringDialog(); break;
            case SUBSTRING: str = substring(); break;
            case FROM_CHAR_CODE: str = fromCharCode(); break;
            case GET_INFO: str = getInfo(); break;          
            case GET_IMAGE_INFO: interp.getParens(); str = getImageInfo(); break;           
            case GET_DIRECTORY: str = getDirectory(); break;
            case GET_ARGUMENT: interp.getParens(); str=interp.argument!=null?interp.argument:""; break;
            case TO_LOWER_CASE: str = getStringArg().toLowerCase(Locale.US); break;
            case TO_UPPER_CASE: str = getStringArg().toUpperCase(Locale.US); break;
            case RUN_MACRO: str = runMacro(false); break;
            case EVAL: str = runMacro(true); break;
            case TO_STRING: str = doToString(); break;
            case REPLACE: str = replace(); break;
            case DIALOG: str = doDialog(); break;
            case GET_METADATA: str = getMetadata(); break;
            case FILE: str = doFile(); break;
            case SELECTION_NAME: str = selectionName(); break;
            case GET_VERSION: interp.getParens();  str = IJ.getVersion(); break;
            case GET_RESULT_LABEL: str = getResultLabel(); break;
            case CALL: str = call(); break;
            case STRING: str = doString(); break;
            case EXT: str = doExt(); break;
            case EXEC: str = exec(); break;
            case LIST: str = doList(); break;
            default:
                str="";
                interp.error("String function expected");
        }
        return str;
    }

    Variable[] getArrayFunction(int type) {
        Variable[] array;
        switch (type) {
            case GET_PROFILE: array=getProfile(); break;
            case NEW_ARRAY: array = newArray(); break;
            case SPLIT: array = split(); break;
            case GET_FILE_LIST: array = getFileList(); break;
            case GET_FONT_LIST: array = getFontList(); break;
            case NEW_MENU: array = newMenu(); break;
            case GET_LIST: array = getList(); break;
            default:
                array = null;
                interp.error("Array function expected");
        }
        return array;
    }

    final double math(int type) {
        double arg = getArg();
        switch (type) {
            case ABS: return Math.abs(arg);
            case COS: return Math.cos(arg);
            case EXP: return Math.exp(arg);
            case FLOOR: return Math.floor(arg);
            case LOG: return Math.log(arg);
            case ROUND: return Math.floor(arg + 0.5);
            case SIN: return Math.sin(arg);
            case SQRT: return Math.sqrt(arg);
            case TAN: return Math.tan(arg);
            case ATAN: return Math.atan(arg);
            case ASIN: return Math.asin(arg);
            case ACOS: return Math.acos(arg);
            default: return 0.0;
        }
    }

    final double math2(int type) {
        double a1 = getFirstArg();
        double a2 = getLastArg();
        switch (type) {
            case MIN_OF: return Math.min(a1, a2);
            case MAX_OF: return Math.max(a1, a2);
            case POW: return Math.pow(a1, a2);
            case ATAN2: return Math.atan2(a1, a2);
            default: return 0.0;
        }
    }

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

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

    final double getArg() {
        interp.getLeftParen();
        double arg = interp.getExpression();
        interp.getRightParen();
        return arg;
    }

    final double getFirstArg() {
        interp.getLeftParen();
        return interp.getExpression();
    }

    final double getNextArg() {
        interp.getComma();
        return interp.getExpression();
    }

    final double getLastArg() {
        interp.getComma();
        double arg = interp.getExpression();
        interp.getRightParen();
        return arg;
    }

    String getStringArg() {
        interp.getLeftParen();
        String arg = getString();
        interp.getRightParen();
        return arg;
    }

    final String getFirstString() {
        interp.getLeftParen();
        return getString();
    }

    final String getNextString() {
        interp.getComma();
        return getString();
    }

    final String getLastString() {
        interp.getComma();
        String arg = getString();
        interp.getRightParen();
        return arg;
    }

    boolean getBooleanArg() {
        interp.getLeftParen();
        double arg = interp.getBooleanExpression();
        interp.checkBoolean(arg);
        interp.getRightParen();
        return arg==0?false:true;
    }

    final Variable getVariableArg() {
        interp.getLeftParen();
        Variable v = getVariable();
        interp.getRightParen();
        return v;
    }

    final Variable getFirstVariable() {
        interp.getLeftParen();
        return getVariable();
    }

    final Variable getNextVariable() {
        interp.getComma();
        return getVariable();
    }

    final Variable getLastVariable() {
        interp.getComma();
        Variable v = getVariable();
        interp.getRightParen();
        return v;
    }

    final Variable getVariable() {
        interp.getToken();
        if (interp.token!=WORD)
            interp.error("Variable expected");
        Variable v = interp.lookupLocalVariable(interp.tokenAddress);
        if (v==null)
                v = interp.push(interp.tokenAddress, 0.0, null, interp);
        Variable[] array = v.getArray();
        if (array!=null) {
            int index = interp.getIndex();
            checkIndex(index, 0, array.length-1);
            v = array[index]; 
        }
        return v;
    }

    final Variable getFirstArrayVariable() {
        interp.getLeftParen();
        return getArrayVariable();
    }

    final Variable getNextArrayVariable() {
        interp.getComma();
        return getArrayVariable();
    }

    final Variable getLastArrayVariable() {
        interp.getComma();
        Variable v = getArrayVariable();
        interp.getRightParen();
        return v;
    }

    final Variable getArrayVariable() {
        interp.getToken();
        if (interp.token!=WORD)
            interp.error("Variable expected");
        Variable v = interp.lookupLocalVariable(interp.tokenAddress);
        if (v==null)
                v = interp.push(interp.tokenAddress, 0.0, null, interp);
        return v;
    }

    final double[] getFirstArray() {
        interp.getLeftParen();
        return getNumericArray();
    }

    final double[] getNextArray() {
        interp.getComma();
        return getNumericArray();
    }

    final double[] getLastArray() {
        interp.getComma();
        double[] a = getNumericArray();
        interp.getRightParen();
        return a;
    }

    double[] getNumericArray() {
        Variable[] a1 = getArray();
        double[] a2 = new double[a1.length];
        for (int i=0; i<a1.length; i++)
            a2[i] = a1[i].getValue();
        return a2;
    }

    String[] getStringArray() {
        Variable[] a1 = getArray();
        String[] a2 = new String[a1.length];
        for (int i=0; i<a1.length; i++) {
            String s = a1[i].getString();
            if (s==null) s = "" + a1[i].getValue();
            a2[i] = s;
        }
        return a2;
    }

    Variable[] getArray() {
        interp.getToken();
        boolean newArray = interp.token==ARRAY_FUNCTION && pgm.table[interp.tokenAddress].type==NEW_ARRAY;
        if (!(interp.token==WORD||newArray))
            interp.error("Array expected");
        Variable[] a;
        if (newArray)
            a = getArrayFunction(NEW_ARRAY);
        else {
            Variable v = interp.lookupVariable();
            a= v.getArray();
        }
        if (a==null)
            interp.error("Array expected");
        return a;
    }
        
    Color getColor() {
        String color = getString();
        color = color.toLowerCase(Locale.US);
        if (color.equals("black"))
            return Color.black;
        else if (color.equals("white"))
            return Color.white;
        else if (color.equals("red"))
            return Color.red;
        else if (color.equals("green"))
            return Color.green;
        else if (color.equals("blue"))
            return Color.blue;
        else if (color.equals("cyan"))
            return Color.cyan;
        else if (color.equals("darkgray"))
            return Color.darkGray;
        else if (color.equals("gray"))
            return Color.gray;
        else if (color.equals("lightgray"))
            return Color.lightGray;
        else if (color.equals("magenta"))
            return Color.magenta;
        else if (color.equals("orange"))
            return Color.orange;
        else if (color.equals("yellow"))
            return Color.yellow;
        else if (color.equals("pink"))
            return Color.pink;
        else
            interp.error("'red', 'green', etc. expected");
        return null;
    }

    void checkIndex(int index, int lower, int upper) {
        if (index<lower || index>upper)
            interp.error("Index ("+index+") is outside of the "+lower+"-"+upper+" range");
    }

    void doRun() {
        interp.getLeftParen();
        String arg1 = getString();
        interp.getToken();
        if (!(interp.token==')' || interp.token==','))
            interp.error("',' or ')'  expected");
        String arg2 = null;
        if (interp.token==',') {
            arg2 = getString();
            interp.getRightParen();
        }
        if (arg2!=null)
            IJ.run(arg1, arg2);
        else
            IJ.run(arg1);
        resetImage();
        IJ.setKeyUp(IJ.ALL_KEYS);
        shiftKeyDown = altKeyDown = false;
    }

    void setForegroundColor() {
        IJ.setForegroundColor((int)getFirstArg(), (int)getNextArg(), (int)getLastArg());
        resetImage(); 
        defaultColor = null;
        defaultValue = Double.NaN;
    }

    void setBackgroundColor() {
        IJ.setBackgroundColor((int)getFirstArg(), (int)getNextArg(), (int)getLastArg());
        resetImage(); 
    }

    void setColor() {
        colorSet = true;
        interp.getLeftParen();
        if (isStringArg()) {
            defaultColor = getColor();
            getProcessor().setColor(defaultColor);
            defaultValue = Double.NaN;
            interp.getRightParen();
            return;
        }
        double arg1 = interp.getExpression();
        if (interp.nextToken()==')')
            {interp.getRightParen(); setColor(arg1); return;}
        int red=(int)arg1, green=(int)getNextArg(), blue=(int)getLastArg();
        if (red<0) red=0; if (green<0) green=0; if (blue<0) blue=0; 
        if (red>255) red=255; if (green>255) green=255; if (blue>255) blue=255;  
        defaultColor = new Color(red, green, blue);
        getProcessor().setColor(defaultColor);
        defaultValue = Double.NaN;
    }
    
    void setColor(double value) {
        ImageProcessor ip = getProcessor();
        ImagePlus imp = getImage();
        switch (imp.getBitDepth()) {
            case 8:
                if (value<0 || value>255)
                    interp.error("Argument out of 8-bit range (0-255)");
                ip.setValue(value);
                break;
            case 16:
                if (imp.getLocalCalibration().isSigned16Bit())
                    value += 32768;
                if (value<0 || value>65535)
                    interp.error("Argument out of 16-bit range (0-65535)");
                ip.setValue(value);
                break;
            default:
                ip.setValue(value);
                break;
        }
        defaultValue = value;
        defaultColor = null;
    }

    void makeLine() {
        double x1d = getFirstArg();
        double y1d = getNextArg();
        double x2d = getNextArg();
        interp.getComma();
        double y2d = interp.getExpression();
        interp.getToken();
        if (interp.token==')')
            IJ.makeLine(x1d, y1d, x2d, y2d);
        else {
            int x1 = (int)Math.round(x1d);
            int y1 = (int)Math.round(y1d);
            int x2 = (int)Math.round(x2d);
            int y2 = (int)Math.round(y2d);
            int max = 200;
            int[] x = new int[max];
            int[] y = new int[max];
            x[0]=x1; y[0]=y1; x[1]=x2; y[1]=y2;
            int n = 2;
            while (interp.token==',' && n<max) {
                x[n] = (int)Math.round(interp.getExpression());
                if (n==2 && interp.nextToken()==')') {
                    interp.getRightParen();
                    Line.setWidth((int)x[n]);
                    IJ.makeLine(x1, y1, x2, y2);
                    return;
                }
                interp.getComma();
                y[n] = (int)Math.round(interp.getExpression());
                interp.getToken();
                n++;
            }
            if (n==max && interp.token!=')')
                interp.error("More than "+max+" points");
            getImage().setRoi(new PolygonRoi(x, y, n, Roi.POLYLINE));
        }
        resetImage(); 
    }

    void makeOval() {
        Roi previousRoi = getImage().getRoi();
        if (shiftKeyDown||altKeyDown) getImage().saveRoi();
        IJ.makeOval((int)getFirstArg(), (int)getNextArg(), (int)getNextArg(), (int)getLastArg());
        Roi roi = getImage().getRoi();
        if (previousRoi!=null && roi!=null)
            updateRoi(roi);
        resetImage();
    }
    
    void makeRectangle() {
        Roi previousRoi = getImage().getRoi();
        if (shiftKeyDown||altKeyDown) getImage().saveRoi();
        IJ.makeRectangle((int)getFirstArg(), (int)getNextArg(), (int)getNextArg(), (int)getLastArg());
        Roi roi = getImage().getRoi();
        if (previousRoi!=null && roi!=null)
            updateRoi(roi);
        resetImage();
    }
    
    ImagePlus getImage() {
        if (defaultImp==null)
            defaultImp = IJ.getImage();
        if (defaultImp==null)
            {interp.error("No image"); return null;}    
        if (defaultImp.getWindow()==null && IJ.getInstance()!=null && !interp.isBatchMode() && WindowManager.getTempCurrentImage()==null)
            throw new RuntimeException(Macro.MACRO_CANCELED);
        return defaultImp;
    }
    
    void resetImage() {
        defaultImp = null;
        defaultIP = null;
        colorSet = fontSet = false;
    }

    ImageProcessor getProcessor() {
        if (defaultIP==null) {
            defaultImp = getImage();
            defaultIP = defaultImp.getProcessor();
        }
        return defaultIP;
    }

    int getType() {
        if (defaultImp==null)
            defaultImp = IJ.getImage();
        imageType = defaultImp.getType();
        return imageType;
    }

    void setPixel() {
        interp.getLeftParen();
        int a1 = (int)interp.getExpression();
        interp.getComma();
        double a2 = interp.getExpression();
        interp.getToken();
        if (interp.token==',') {
            double a3 = interp.getExpression();
            interp.getRightParen();
            if (getType()==ImagePlus.GRAY32)
                getProcessor().putPixelValue(a1, (int)a2, a3);
            else
                getProcessor().putPixel(a1, (int)a2, (int)a3);
        } else {
            if (interp.token!=')') interp.error("')' expected");
            getProcessor().setf(a1, (float)a2);
        }
        updateNeeded = true;
    }

    double getPixel() {
        interp.getLeftParen();
        int a1 = (int)interp.getExpression();
        ImageProcessor ip = getProcessor();
        double value = 0.0;
        interp.getToken();
        if (interp.token==',') {
            int a2 = (int)interp.getExpression();
            interp.getRightParen();
            if (getType()==ImagePlus.GRAY32)
                value = ip.getPixelValue(a1, a2);
            else
                value = ip.getPixel(a1, a2);
        } else {
            if (interp.token!=')') interp.error("')' expected");
            value = ip.getf(a1);
        }
        return value;
    }
    
    void setZCoordinate() {
        int z = (int)getArg();
        ImagePlus imp = getImage();
        ImageStack stack = imp.getStack();
        int size = stack.getSize();
        if (z<0 || z>=size)
            interp.error("Z coordinate ("+z+") is out of 0-"+(size-1)+ " range");
        this.defaultIP = stack.getProcessor(z+1);       
    }
    
    void moveTo() {
        interp.getLeftParen();
        int a1 = (int)(interp.getExpression()+0.5);
        interp.getComma();
        int a2 = (int)(interp.getExpression()+0.5);
        interp.getRightParen();
        getProcessor().moveTo(a1, a2);
    }
    
    void lineTo() {
        interp.getLeftParen();
        int a1 = (int)(interp.getExpression()+0.5);
        interp.getComma();
        int a2 = (int)(interp.getExpression()+0.5);
        interp.getRightParen();
        ImageProcessor ip = getProcessor();
        if (!colorSet) setForegroundColor(ip);
        ip.lineTo(a1, a2);
        updateAndDraw(defaultImp);
    }

    void drawLine() {
        interp.getLeftParen();
        int x1 = (int)(interp.getExpression()+0.5);
        interp.getComma();
        int y1 = (int)(interp.getExpression()+0.5);
        interp.getComma();
        int x2 = (int)(interp.getExpression()+0.5);
        interp.getComma();
        int y2 = (int)(interp.getExpression()+0.5);
        interp.getRightParen();
        ImageProcessor ip = getProcessor();
        if (!colorSet) setForegroundColor(ip);
        ip.drawLine(x1, y1, x2, y2);
        updateAndDraw(defaultImp);
    }
    
    void setForegroundColor(ImageProcessor ip) {
        if (defaultColor!=null)
            ip.setColor(defaultColor);
        else if (!Double.isNaN(defaultValue))
            ip.setValue(defaultValue);
        else
            ip.setColor(Toolbar.getForegroundColor());
        colorSet = true;
    }

    void doIPMethod(int type) {
        interp.getParens(); 
        ImageProcessor ip = getProcessor();
        switch (type) {
            case SNAPSHOT: ip.snapshot(); break;
            case RESET:
                ip.reset();
                updateNeeded = true;
                break;
            case FILL: 
                ImagePlus imp = getImage();
                Roi roi = imp.getRoi();
                if (!colorSet) setForegroundColor(ip);
                if (roi==null) {
                    ip.resetRoi();
                    ip.fill();
                } else {
                    ip.setRoi(roi);
                    ip.fill(ip.getMask());
                }
                updateAndDraw(imp);
                break;
        }
    }

    void updateAndDraw(ImagePlus imp) {
        if (autoUpdate)
            imp.updateChannelAndDraw();
        else
            updateNeeded = true;
    }
    
    void updateDisplay() {
        if (updateNeeded && WindowManager.getImageCount()>0) {
            ImagePlus imp = getImage();
            imp.updateAndDraw();
            updateNeeded = false;
        }
    }

    void drawString() {
        interp.getLeftParen();
        String str = getString();
        interp.getComma();
        int x = (int)(interp.getExpression()+0.5);
        interp.getComma();
        int y = (int)(interp.getExpression()+0.5);
        interp.getRightParen();
        ImageProcessor ip = getProcessor();
        if (!colorSet)
            setForegroundColor(ip);
        setFont(ip);
        ip.setJustification(justification);
        ip.setAntialiasedText(antialiasedText);
        ip.drawString(str, x, y);
        updateAndDraw(defaultImp);
    }
    
    void setFont(ImageProcessor ip) {
        if (font!=null && !fontSet)
            ip.setFont(font);
        fontSet = true;
    }

    void setJustification() {
        String str = getStringArg().toLowerCase(Locale.US);
        int just = ImageProcessor.LEFT_JUSTIFY;
        if (str.equals("center"))
            just = ImageProcessor.CENTER_JUSTIFY;
        else if (str.equals("right"))
            just = ImageProcessor.RIGHT_JUSTIFY;
        justification = just;
    }

    void changeValues() {
        double darg1 = getFirstArg();
        double darg2 = getNextArg();
        double darg3 = getLastArg();
        ImagePlus imp = getImage();
        ImageProcessor ip = getProcessor();
        Roi roi = imp.getRoi();
        ImageProcessor mask = null;
        if (roi==null || !roi.isArea()) {
            ip.resetRoi();
            roi = null;
        } else {
            ip.setRoi(roi);
            mask = ip.getMask();
            if (mask!=null) ip.snapshot();
        }
        int xmin=0, ymin=0, xmax=imp.getWidth(), ymax=imp.getHeight();
        if (roi!=null) {
            Rectangle r = roi.getBounds();
            xmin=r.x; ymin=r.y; xmax=r.x+r.width; ymax=r.y+r.height;
        }
        boolean isFloat = getType()==ImagePlus.GRAY32;
        if (imp.getBitDepth()==24) {
            darg1 = (int)darg1&0xffffff;
            darg2 = (int)darg2&0xffffff;
        }
        double v;
        for (int y=ymin; y<ymax; y++) {
            for (int x=xmin; x<xmax; x++) {
                v = isFloat?ip.getPixelValue(x,y):ip.getPixel(x,y)&0xffffff;
                if (v>=darg1 && v<=darg2) {
                    if (isFloat)
                        ip.putPixelValue(x, y, darg3);
                    else
                        ip.putPixel(x, y, (int)darg3);
                }
            }
        }
        if (mask!=null) ip.reset(mask);
        if (imp.getType()==ImagePlus.GRAY16 || imp.getType()==ImagePlus.GRAY32)
            ip.resetMinAndMax();
        imp.updateAndDraw();
        updateNeeded = false;
    }

    void requires() {
        if (IJ.versionLessThan(getStringArg()))
            interp.done = true;
    }

    Random ran; 
    double random() {
        interp.getParens();
        if (ran==null)
            ran = new Random();
        return ran.nextDouble();
    }
    
    //void setSeed() {
    //  long seed = (long)getArg();
    //  if (ran==null)
    //      ran = new Random(seed);
    //  else
    //      ran.setSeed(seed);
    //}

    double getResult() {
        interp.getLeftParen();
        String column = getString();
        int row = -1;
        if (interp.nextNonEolToken()==',') {
            interp.getComma();
            row = (int)interp.getExpression();
        }
        interp.getRightParen();
        ResultsTable rt = Analyzer.getResultsTable();
        int counter = rt.getCounter();
        if (counter==0)
            interp.error("\"Results\" table empty");
        if (row==-1) row = counter-1;
        if (row<0 || row>=counter)
            interp.error("Row ("+row+") out of range");
        int col = rt.getColumnIndex(column);
        if (!rt.columnExists(col))
            return Double.NaN;
        else
            return rt.getValueAsDouble(col, row);
    }

    String getResultLabel() {
        int row = (int)getArg();
        ResultsTable rt = Analyzer.getResultsTable();
        int counter = rt.getCounter();
        if (counter==0)
            interp.error("\"Results\" table empty");
        if (row<0 || row>=counter)
            interp.error("Row ("+row+") out of range");
        String label = rt.getLabel(row);
        return label!=null?label:"";
    }

    void setResult() {
        interp.getLeftParen();
        String column = getString();
        interp.getComma();
        int row = (int)interp.getExpression();
        interp.getComma();
        double value = 0.0;
        String label = null;
        if (column.equals("Label"))
            label = getString();
        else
            value = interp.getExpression();     
        interp.getRightParen();
        ResultsTable rt = Analyzer.getResultsTable();
        if (row<0 || row>rt.getCounter())
            interp.error("Row ("+row+") out of range");
        if (row==rt.getCounter())
            rt.incrementCounter();
        try {
            if (label!=null)
                rt.setLabel(label, row);
            else
                rt.setValue(column, row, value);
        } catch (Exception e) {
            interp.error(""+e.getMessage());
        }
    }
    
    void updateResults() {
        interp.getParens();
        ResultsTable rt = Analyzer.getResultsTable();
        rt.show("Results");
    }

    double getNumber() {
        String prompt = getFirstString();
        double defaultValue = getLastArg();
        String title = interp.macroName!=null?interp.macroName:"";
        if (title.endsWith(" Options"))
            title = title.substring(0, title.length()-8);
        GenericDialog gd = new GenericDialog(title);
        int decimalPlaces = (int)defaultValue==defaultValue?0:2;
        gd.addNumericField(prompt, defaultValue, decimalPlaces);
        gd.showDialog();
        if (gd.wasCanceled()) {
            interp.done = true;
            return defaultValue;
        }
        double v = gd.getNextNumber();
        if (gd.invalidNumber())
            return defaultValue;
        else
            return v;
    }

    double getBoolean() {
        String prompt = getStringArg();
        String title = interp.macroName!=null?interp.macroName:"";
        if (title.endsWith(" Options"))
            title = title.substring(0, title.length()-8);
        YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), title, prompt);
        if (d.cancelPressed()) {
            interp.done = true;
            return 0.0;
        } else if (d.yesPressed())
            return 1.0;
        else
            return 0.0;
    }

    double getBoolean2() {
        String prompt = getFirstString();
        interp.getComma();
        double defaultValue = interp.getBooleanExpression();
        interp.checkBoolean(defaultValue);
        interp.getRightParen();
        String title = interp.macroName!=null?interp.macroName:"";
        if (title.endsWith(" Options"))
            title = title.substring(0, title.length()-8);
        GenericDialog gd = new GenericDialog(title);
        gd.addCheckbox(prompt, defaultValue==1.0?true:false);
        gd.showDialog();
        if (gd.wasCanceled()) {
            interp.done = true;
            return 0.0;
        }
        return gd.getNextBoolean()?1.0:0.0;
    }
    
    String getStringDialog() {
        interp.getLeftParen();
        String prompt = getString();
        interp.getComma();
        String defaultStr = getString();
        interp.getRightParen();
        
        String title = interp.macroName!=null?interp.macroName:"";
        if (title.endsWith(" Options"))
            title = title.substring(0, title.length()-8);
        GenericDialog gd = new GenericDialog(title);
        gd.addStringField(prompt, defaultStr, 20);
        gd.showDialog();
        String str = "";
        if (gd.wasCanceled())
            interp.done = true;
        else
            str = gd.getNextString();
        return str;
    }

    String d2s() {
        return IJ.d2s(getFirstArg(), (int)getLastArg());
    }

    String toString(int base) {
        int arg = (int)getArg();
        if (base==2)
            return Integer.toBinaryString(arg);
        else
            return Integer.toHexString(arg);
    }
    
    double getStackSize() {
        interp.getParens();
        return getImage().getStackSize();
    }
    
    double getImageCount() {
        interp.getParens();
        return WindowManager.getImageCount();
    }
    
    double getResultsCount() {
        interp.getParens();
        return Analyzer.getResultsTable().getCounter();
    }

    void getCoordinates() {
        Variable xCoordinates = getFirstArrayVariable();
        Variable yCoordinates = getLastArrayVariable();
        resetImage();
        ImagePlus imp = getImage();
        Roi roi = imp.getRoi();
        if (roi==null)
            interp.error("Selection required");
        Polygon p = roi.getPolygon();
        FloatPolygon fp = roi.getFloatPolygon();
        Variable[] xa = new Variable[p.npoints];
        Variable[] ya = new Variable[p.npoints];
        if (fp!=null) { //spline fit polygon
            for (int i=0; i<p.npoints; i++)
            xa[i] = new Variable(fp.xpoints[i]);
            for (int i=0; i<p.npoints; i++)
            ya[i] = new Variable(fp.ypoints[i]);
        } else {
            for (int i=0; i<p.npoints; i++)
            xa[i] = new Variable(p.xpoints[i]);
            for (int i=0; i<p.npoints; i++)
            ya[i] = new Variable(p.ypoints[i]);
        }
        xCoordinates.setArray(xa);
        yCoordinates.setArray(ya);
    }
    
    Variable[] getProfile() {
        interp.getParens();
        ImagePlus imp = getImage();
        if (imp.getRoi()==null)
            interp.error("Selection required");
        ProfilePlot pp = new ProfilePlot(imp, IJ.altKeyDown());
        double[] array = pp.getProfile();
        if (array==null)
            {interp.done=true; return null;}
        else
            return new Variable(array).getArray();
    }

    Variable[] newArray() {
        interp.getLeftParen();
        int next = interp.nextNonEolToken();
        if (next==STRING_CONSTANT || interp.nextNextNonEolToken()==','
        || next=='-' || next==PI)
            return initNewArray();
        int size = (int)interp.getExpression();
        interp.getRightParen();
        Variable[] array = new Variable[size];
        for (int i=0; i<size; i++)
            array[i] = new Variable();
        return array;
    }
    
    Variable[] split() {
        String s1 = getFirstString();
        String s2 = null;
        if (interp.nextToken()==')')
            interp.getRightParen();
        else
            s2 = getLastString();
        if (s1==null) return null;
        String[] strings = (s2==null||s2.equals(""))?Tools.split(s1):Tools.split(s1, s2);
        Variable[] array = new Variable[strings.length];
        for (int i=0; i<strings.length; i++)
            array[i] = new Variable(0, 0.0, strings[i]);
        return array;
    }

    Variable[] getFileList() {
        String dir = getStringArg();
        File f = new File(dir);
        if (!f.exists() || !f.isDirectory())
            return new Variable[0];
        String[] list = f.list();
        if (list==null)
            return new Variable[0];
        if (System.getProperty("os.name").indexOf("Linux")!=-1)
            ij.util.StringSorter.sort(list);
        File f2;
        int hidden = 0;
        for (int i=0; i<list.length; i++) {
            if (list[i].startsWith(".")) {
                list[i] = null;
                hidden++;
            } else {
                f2 = new File(dir, list[i]);
                if (f2.isDirectory())
                    list[i] = list[i] + "/";
            }
        }
        int n = list.length-hidden;
        if (n<=0)
            return new Variable[0];
        if (hidden>0) {
            String[] list2 = new String[n];
            int j = 0;
            for (int i=0; i<list.length; i++) {
                if (list[i]!=null)
                    list2[j++] = list[i];
            }
            list = list2;
        }
        Variable[] array = new Variable[n];
        for (int i=0; i<n; i++)
            array[i] = new Variable(0, 0.0, list[i]);
        return array;
    }
    
    Variable[] initNewArray() {
        Vector vector = new Vector();
        int size = 0;
        boolean stringArray = false;
        do {
            Variable v = new Variable();
            int tok = interp.nextNonEolToken();
            if (tok==STRING_CONSTANT||tok==STRING_FUNCTION||(tok==WORD&&stringArray)) {
                v.setString(getString());
                stringArray = true;
            } else
                v.setValue(interp.getExpression());
            vector.addElement(v);
            size++;
            interp.getToken();              
        } while (interp.token==',');
        if (interp.token!=')')
            interp.error("';' expected");
        Variable[] array = new Variable[size];
        vector.copyInto((Variable[])array);
        return array;
    }

    String fromCharCode() {
        char[] chars = new char[100];
        int count = 0;
        interp.getLeftParen();
        while(interp.nextToken()!=')') {
            int value = (int)interp.getExpression();
            if (value<0 || value>65535)
                interp.error("Value (" + value + ") out of 0-65535 range");
            chars[count++] = (char)value;
            if (interp.nextToken()==',')
                interp.getToken();
        }
        interp.getRightParen();     
        return new String(chars, 0, count);
    }

    String getInfo() {
        if (interp.nextNextNonEolToken()==STRING_CONSTANT
        || (interp.nextNonEolToken()=='('&&interp.nextNextNonEolToken()!=')'))
            return getInfo(getStringArg());
        else {
            interp.getParens();
            return getWindowContents();
        }
    }
    
    String getInfo(String key) {
            if (key.equals("image.subtitle")) {
                ImagePlus imp = getImage();
                ImageWindow win = imp.getWindow();
                return win!=null?win.createSubtitle():"";
            } else if (key.equals("slice.label")) {
                ImagePlus imp = getImage();
                if (imp.getStackSize()==1) return "";
                String label = imp.getStack().getShortSliceLabel(imp.getCurrentSlice());
                return label!=null?label:"";
            } else if (key.equals("window.contents")) {
                return getWindowContents();
            } else if (key.equals("image.description")) {
                String description = "";
                FileInfo fi = getImage().getOriginalFileInfo();
                if (fi!=null) description = fi.description;
                if  (description==null) description = "";
                return description;
            } else {
                String value = "";
                try {value = System.getProperty(key);}
                catch (Exception e) {};
                return value!=null?value:"";
            }
    }
    
    String getWindowContents() {
        Frame frame = WindowManager.getFrontWindow();
        if (frame!=null && frame instanceof TextWindow) {
            TextPanel tp = ((TextWindow)frame).getTextPanel();
            return tp.getText();            
        } else if (frame!=null && frame instanceof Editor) {
            return ((Editor)frame).getText();           
        } else if (frame!=null && frame instanceof Recorder) {
            return ((Recorder)frame).getText();         
        } else
            return getImageInfo();
    }
    
    String getImageInfo() {     
        ImagePlus imp = getImage();
        Info infoPlugin = new Info();
        return infoPlugin.getImageInfo(imp, getProcessor());
    }

    public String getDirectory() {
        String dir = IJ.getDirectory(getStringArg());
        if (dir==null) dir = "";
        return dir;
    }

    double getSelectionType() {
        interp.getParens();
        double type = -1;
        ImagePlus imp = getImage();
        Roi roi = imp.getRoi();
        if (roi!=null)
            type = roi.getType();
        return type;
    }

    void showMessage(boolean withCancel) {
        String message;
        interp.getLeftParen();
        String title = getString();
        if (interp.nextToken()==',') {
            interp.getComma();
            message = getString();
        } else {
            message = title;
            title = "";
        }
        interp.getRightParen();
        if (withCancel)
            IJ.showMessageWithCancel(title, message);
        else
            IJ.showMessage(title, message);
    }
    
    double lengthOf() {
        int length = 0;
        interp.getLeftParen();
        switch (interp.nextToken()) {
            case STRING_CONSTANT:
            case STRING_FUNCTION:
            case USER_FUNCTION:
                length = getString().length();
                break; 
            case WORD:
                if (pgm.code[interp.pc+2]=='[') {
                    length = getString().length();
                    break;
                }
                interp.getToken();
                Variable v = interp.lookupVariable();
                if (v==null) return 0.0;
                String s = v.getString();
                if (s!=null)
                    length = s.length();
                else {
                    Variable[] array = v.getArray();
                    if (array!=null)
                        length = array.length;
                    else
                        interp.error("String or array expected");
                }                   
                break;
            default:
                interp.error("String or array expected");
        }
        interp.getRightParen();
        return length;
    }
    
    void getCursorLoc() {
        Variable x = getFirstVariable();
        Variable y = getNextVariable();
        Variable z = getNextVariable();
        Variable flags = getLastVariable();
        ImagePlus imp = getImage();
        ImageCanvas ic = imp.getCanvas();
        if (ic==null) return;
        Point p = ic.getCursorLoc();
        x.setValue(p.x);
        y.setValue(p.y);
        z.setValue(imp.getCurrentSlice()-1);
        flags.setValue(ic.getModifiers());
    }
    
    void getLine() {
        Variable vx1 = getFirstVariable();
        Variable vy1 = getNextVariable();
        Variable vx2 = getNextVariable();
        Variable vy2 = getNextVariable();
        Variable lineWidth = getLastVariable();
        resetImage();
        ImagePlus imp = getImage();
        double x1=-1, y1=-1, x2=-1, y2=-1;
        Roi roi = imp.getRoi();
        if (roi!=null && roi.getType()==Roi.LINE) {
            Line line = (Line)roi;
            x1=line.x1d; y1=line.y1d; x2=line.x2d; y2=line.y2d;
        }
        vx1.setValue(x1);
        vy1.setValue(y1);
        vx2.setValue(x2);
        vy2.setValue(y2);               
        lineWidth.setValue(Line.getWidth());                
    }
    
    void getVoxelSize() {
        Variable width = getFirstVariable();
        Variable height = getNextVariable();
        Variable depth = getNextVariable();
        Variable unit = getLastVariable();
        resetImage();
        ImagePlus imp = getImage();
        Calibration cal = imp.getCalibration();
        width.setValue(cal.pixelWidth);
        height.setValue(cal.pixelHeight);
        depth.setValue(cal.pixelDepth);
        unit.setString(cal.getUnits());
    }
    
    void getHistogram() {
        interp.getLeftParen();
        Variable values = null;
        if (interp.nextToken()==NUMBER)
            interp.getExpression();
        else
            values = getArrayVariable();
        Variable counts = getNextArrayVariable();
        interp.getComma();
        int nBins = (int)interp.getExpression();
        ImagePlus imp = getImage();
        double histMin=0.0, histMax=0.0;
        boolean setMinMax = false;
        int bitDepth = imp.getBitDepth();
        if (interp.nextToken()==',') {
            histMin = getNextArg();
            histMax = getLastArg();
            if (bitDepth==8 || bitDepth==24)
                interp.error("16 or 32-bit image required to set histMin and histMax");
            setMinMax = true;
        } else 
            interp.getRightParen();
        if ((bitDepth==8||bitDepth==24) && nBins!=256)
            interp.error("Bin count ("+nBins+") must be 256 for 8-bit and RGB images");
        if (nBins==65536 && bitDepth==16) {
            Variable[] array = counts.getArray();
            int[] hist = getProcessor().getHistogram();
            if (array!=null && array.length==nBins) {
                for (int i=0; i<nBins; i++)
                    array[i].setValue(hist[i]);
            } else
                counts.setArray(new Variable(hist).getArray());
            return;
        }
        ImageStatistics stats;
        if (setMinMax)
            stats = imp.getStatistics(AREA+MEAN+MODE+MIN_MAX, nBins, histMin, histMax);
        else
            stats = imp.getStatistics(AREA+MEAN+MODE+MIN_MAX, nBins);
        if (values!=null) {
            Calibration cal = imp.getCalibration();
            double[] array = new double[nBins];
            double value = cal.getCValue(stats.histMin);
            double inc = 1.0;
            if (bitDepth==16 || bitDepth==32 || cal.calibrated())
                inc = (cal.getCValue(stats.histMax) - cal.getCValue(stats.histMin))/stats.nBins;
            for (int i=0; i<nBins; i++) {
                array[i] = value;
                value += inc;
            }
            values.setArray(new Variable(array).getArray());
        }
        Variable[] array = counts.getArray();
        if (array!=null && array.length==nBins) {
            for (int i=0; i<nBins; i++)
                array[i].setValue(stats.histogram[i]);
        } else
            counts.setArray(new Variable(stats.histogram).getArray());
    }
    
    void getLut() {
        Variable reds = getFirstArrayVariable();
        Variable greens = getNextArrayVariable();
        Variable blues = getLastArrayVariable();
        resetImage();
        ImagePlus imp = getImage();
        IndexColorModel cm = null;
        if (imp.isComposite())
            cm = ((CompositeImage)imp).getChannelLut();
        else {
            ImageProcessor ip = imp.getProcessor();
            if (ip instanceof ColorProcessor)
                interp.error("Non-RGB image expected");
            cm = (IndexColorModel)ip.getColorModel();
        }
        int mapSize = cm.getMapSize();
        byte[] rLUT = new byte[mapSize];
        byte[] gLUT = new byte[mapSize];
        byte[] bLUT = new byte[mapSize];
        cm.getReds(rLUT); 
        cm.getGreens(gLUT); 
        cm.getBlues(bLUT);
        reds.setArray(new Variable(rLUT).getArray());
        greens.setArray(new Variable(gLUT).getArray());
        blues.setArray(new Variable(bLUT).getArray());
    }

    void setLut() {
        double[] reds = getFirstArray();
        double[] greens = getNextArray();
        double[] blues = getLastArray();
        int length = reds.length;       
        if (greens.length!=length || blues.length!=length)
            interp.error("Arrays are not the same length");
        resetImage();
        ImagePlus imp = getImage();
        if (imp.getBitDepth()==24)
            interp.error("Non-RGB image expected");
        ImageProcessor ip = getProcessor();
        byte[] r = new byte[length];
        byte[] g = new byte[length];
        byte[] b = new byte[length];
        for (int i=0; i<length; i++) {
            r[i] = (byte)reds[i];
            g[i] = (byte)greens[i];
            b[i] = (byte)blues[i];
        }
        LUT lut = new LUT(8, length, r, g, b);
        if (imp.isComposite())
            ((CompositeImage)imp).setChannelLut(lut);
        else
            ip.setColorModel(lut);
        imp.updateAndDraw();
        updateNeeded = false;
    }

    void getThreshold() {
        Variable lower = getFirstVariable();
        Variable upper = getLastVariable();
        ImagePlus imp = getImage();
        ImageProcessor ip = getProcessor();
        double t1 = ip.getMinThreshold();
        double t2 = ip.getMaxThreshold();
        if (t1==ImageProcessor.NO_THRESHOLD) {
            t1 = -1;
            t2 = -1;
        } else if (imp.getBitDepth()==16) {
            Calibration cal = imp.getCalibration();
            t1 = cal.getCValue(t1); 
            t2 = cal.getCValue(t2); 
        }

        lower.setValue(t1);
        upper.setValue(t2);
    }

    void getPixelSize() {
        Variable unit = getFirstVariable();
        Variable width = getNextVariable();
        Variable height = getNextVariable();
        Variable depth = null;
        if (interp.nextToken()==',')
            depth = getNextVariable();
        interp.getRightParen();
        Calibration cal = getImage().getCalibration();
        unit.setString(cal.getUnits());
        width.setValue(cal.pixelWidth);
        height.setValue(cal.pixelHeight);
        if (depth!=null)
        depth.setValue(cal.pixelDepth);
    }

    void makeSelection() {
        String type = null;
        int roiType = -1;
        interp.getLeftParen();
        if (isStringArg()) {
            type = getString().toLowerCase();
            roiType = Roi.POLYGON;
            if (type.indexOf("free")!=-1)
                roiType = Roi.FREEROI;
            if (type.indexOf("traced")!=-1)
                roiType = Roi.TRACED_ROI;
            if (type.indexOf("line")!=-1) {
                if (type.indexOf("free")!=-1)
                    roiType = Roi.FREELINE;
                else
                    roiType = Roi.POLYLINE;
            }
            if (type.indexOf("angle")!=-1)
                roiType = Roi.ANGLE;
            if (type.indexOf("point")!=-1)
                roiType = Roi.POINT;
        } else {
            roiType = (int)interp.getExpression();
            if (roiType<0 || roiType==Roi.COMPOSITE)
                interp.error("Invalid selection type ("+roiType+")");
            if (roiType==Roi.RECTANGLE) roiType = Roi.POLYGON;      
            if (roiType==Roi.OVAL) roiType = Roi.FREEROI;       
        }
        double[] x = getNextArray();
        double[] y = getLastArray();
        int n = x.length;       
        if (y.length!=n)
            interp.error("Arrays are not the same length");
        resetImage();
        ImagePlus imp = getImage();
        int[] xcoord = new int[n];
        int[] ycoord = new int[n];
        int height = imp.getHeight();
        for (int i=0; i<n; i++) {
            xcoord[i] = (int)Math.round(x[i]);
            ycoord[i] = (int)Math.round(y[i]);
        }
        Roi roi = null;
        if (roiType==Roi.LINE) {
            if (xcoord.length!=2)
                interp.error("2 element arrays expected");
            roi = new Line(xcoord[0], ycoord[0], xcoord[1], ycoord[1]);
        } else if (roiType==Roi.POINT)
            roi = new PointRoi(xcoord, ycoord, n);
        else
            roi = new PolygonRoi(xcoord, ycoord, n, roiType);
        Roi previousRoi = imp.getRoi();
        if (shiftKeyDown||altKeyDown) imp.saveRoi();
        imp.setRoi(roi);
        if (roiType==Roi.POLYGON || roiType==Roi.FREEROI) {
            roi = imp.getRoi();
            if (previousRoi!=null && roi!=null)
                updateRoi(roi); 
        }
        updateNeeded = false;
    }

    void doPlot() {
        interp.getToken();
        if (interp.token!='.')
            interp.error("'.' expected");
        interp.getToken();
        if (!(interp.token==WORD || interp.token==PREDEFINED_FUNCTION))
            interp.error("Function name expected: ");
        String name = interp.tokenString;
        if (name.equals("create")) {
            newPlot();
            return;
        }
        if (plot==null)
            interp.error("No plot defined");
        if (name.equals("show")) {
            showPlot();
            return;
        } else if (name.equals("update")) {
            updatePlot(); 
            return;
        } else if (name.equals("setLimits")) {
            plot.setLimits(getFirstArg(), getNextArg(), getNextArg(), getLastArg());
            return;
        } else if (name.equals("addText") || name.equals("drawLabel")) {
            addPlotText(); 
            return;
        } else if (name.equals("setColor")) {
            setPlotColor(); 
            return;
        } else if (name.equals("add")) {
            String arg = getFirstString();
            arg = arg.toLowerCase(Locale.US);
            int what = Plot.CIRCLE;
            if (arg.indexOf("curve")!=-1 || arg.indexOf("line")!=-1)
                what = Plot.LINE;
            else if (arg.indexOf("box")!=-1)
                what = Plot.BOX;
            else if (arg.indexOf("triangle")!=-1)
                what = Plot.TRIANGLE;
            else if (arg.indexOf("cross")!=-1)
                what = Plot.CROSS;      
            else if (arg.indexOf("dot")!=-1)
                what = Plot.DOT;        
            else if (arg.indexOf("x")!=-1)
                what = Plot.X;
            else if (arg.indexOf("error")!=-1)
                what = -1;
            addToPlot(what); 
            return;
        } else if (name.startsWith("setLineWidth")) {
            plot.setLineWidth((int)getArg()); 
            return;
        } else if (name.startsWith("setJustification")) {
            doFunction(SET_JUSTIFICATION); 
            return;
        } else
            interp.error("Unrecognized plot function");
    }

    void newPlot() {
        String title = getFirstString();
        String xLabel = getNextString();
        String yLabel = getNextString();
        double[] x, y;
        if (interp.nextToken()==')')
            x = y = null;
        else {
            x = getNextArray();
            if (interp.nextToken()==')') {
                y = x;
                x = new double[y.length];
                for (int i=0; i<y.length; i++)
                    x[i] = i;
            } else
                y = getNextArray();
        }
        interp.getRightParen();
        plot = new Plot(title, xLabel, yLabel, x, y);                           
    }
    
    void showPlot() {
        if (plot!=null) {
            PlotWindow plotWindow = plot.show();
            if (plotWindow!=null)
                plotID = plotWindow.getImagePlus().getID();
        }
        plot = null;
        interp.getParens();         
    }

    void updatePlot() {
        if (plot!=null) {
            ImagePlus plotImage = WindowManager.getImage(plotID);
            ImageWindow win = plotImage!=null?plotImage.getWindow():null;
            if (win!=null)
                ((PlotWindow)win).drawPlot(plot);
            else {
                PlotWindow plotWindow = plot.show();
                plotID = plotWindow.getImagePlus().getID();
            }
        }
        plot = null;
        interp.getParens();         
    }
    
    void addPlotText() {
        String str = getFirstString();
        double x = getNextArg();
        double y = getLastArg();
        plot.setJustification(justification);
        plot.addLabel(x, y, str);
    }

    void setPlotColor() {
        interp.getLeftParen();
        plot.setColor(getColor());
        interp.getRightParen();
    }

    void addToPlot(int what) {
        double[] x = getNextArray();
        double[] y;
        if (interp.nextToken()==')') {
            y = x;
            x = new double[y.length];
            for (int i=0; i<y.length; i++)
                x[i] = i;
        } else {
            interp.getComma();
            y = getNumericArray();
        }
        interp.getRightParen();
        if (what==-1)
            plot.addErrorBars(y);
        else            
            plot.addPoints(x, y, what);     
    }
    
    void getBounds() {
        Variable x = getFirstVariable();
        Variable y = getNextVariable();
        Variable width = getNextVariable();
        Variable height = getLastVariable();
        resetImage();
        ImagePlus imp = getImage();
        Roi roi = imp.getRoi();
        if (roi!=null) {
            Rectangle r = roi.getBounds();
            x.setValue(r.x);
            y.setValue(r.y);
            width.setValue(r.width);
            height.setValue(r.height);
        } else {
            x.setValue(0);
            y.setValue(0);
            width.setValue(imp.getWidth());
            height.setValue(imp.getHeight());
        }
    }

    String substring() {
        String s = getFirstString();
        int index1 = (int)getNextArg();
        int index2 = s.length();
        if (interp.nextToken()==',')
            index2 = (int)getLastArg();
        else
            interp.getRightParen();         
        if (index1>index2)
            interp.error("beginIndex>endIndex");
        checkIndex(index1, 0, s.length());
        checkIndex(index2, 0, s.length());
        return s.substring(index1, index2);
    }

    int indexOf() {
        String s1 = getFirstString();
        String s2 = getNextString();
        int fromIndex = 0;
        if (interp.nextToken()==',') {
            fromIndex = (int)getLastArg();
            checkIndex(fromIndex, 0, s1.length()-1);
        } else
            interp.getRightParen();         
        if (fromIndex==0)
            return s1.indexOf(s2);
        else
            return s1.indexOf(s2, fromIndex);
    }

    int startsWithEndsWith(int type) {
        String s1 = getFirstString();
        String s2 = getLastString();
        if (type==STARTS_WITH)
            return s1.startsWith(s2)?1:0;
        else
            return s1.endsWith(s2)?1:0;
    }

    double isActive() {
        int id = (int)getArg();
        ImagePlus imp = WindowManager.getCurrentImage();
        if (imp==null || imp.getID()!=id)
            return 0.0; //false
        else
            return 1.0; //true
    }
    
    double isOpen() {
        interp.getLeftParen();
        if (isStringArg()) {
            String title = getString();
            interp.getRightParen();
            boolean open = WindowManager.getFrame(title)!=null;
            if (open)
                return 1.0;
            else if (Interpreter.isBatchMode() && Interpreter.imageTable!=null) {
                for (Enumeration en=Interpreter.imageTable.elements(); en.hasMoreElements();) {
                    ImagePlus imp = (ImagePlus)en.nextElement();
                    if (imp!=null && imp.getTitle().equals(title))
                        return 1.0;
                }
            }
            return 0.0;
        } else {
            int id = (int)interp.getExpression();
            interp.getRightParen();
            return WindowManager.getImage(id)==null?0.0:1.0;
        }
    }

    boolean isStringArg() {
        int nextToken = pgm.code[interp.pc+1];
        int tok = nextToken&0xff;
        if (tok==STRING_CONSTANT||tok==STRING_FUNCTION) return true;
        if (tok!=WORD) return false;
        Variable v = interp.lookupVariable(nextToken>>TOK_SHIFT);
        if (v==null) return false;
        int type = v.getType();
        if (type!=Variable.ARRAY)
            return v.getType()==Variable.STRING;
        Variable[] array = v.getArray();
        if (array.length==0) return false;
        return array[0].getType()==Variable.STRING;
    }

    void exit() {
        String msg = null;
        if (interp.nextToken()=='(') {
            interp.getLeftParen();
            if (isStringArg())
                msg = getString();
            interp.getRightParen();
        }
        interp.finishUp();
        if (msg!=null)
            IJ.showMessage("Macro", msg);
        throw new RuntimeException(Macro.MACRO_CANCELED);
    }
    
    void showProgress() {
        ImageJ ij = IJ.getInstance();
        ij.gui.ProgressBar progressBar = ij!=null?ij.getProgressBar():null;
        interp.getLeftParen();
        double arg1 = interp.getExpression();
        if (interp.nextToken()==',') {
            interp.getComma();
            double arg2 = interp.getExpression();
            if (progressBar!=null) progressBar.show((arg1+1.0)/arg2, true);                     
        } else 
            if (progressBar!=null) progressBar.show(arg1, true);
        interp.getRightParen();
        interp.showingProgress = true;  
    }
    
    void saveSettings() {
        interp.getParens();
        usePointerCursor = Prefs.usePointerCursor;
        hideProcessStackDialog = IJ.hideProcessStackDialog;
        divideByZeroValue = FloatBlitter.divideByZeroValue;
        jpegQuality = JpegWriter.getQuality();
        lineWidth = Line.getWidth();
        doScaling = ImageConverter.getDoScaling();
        weightedColor = Prefs.weightedColor;
        weights = ColorProcessor.getWeightingFactors();
        interpolateScaledImages = Prefs.interpolateScaledImages;
        open100Percent = Prefs.open100Percent;
        blackCanvas = Prefs.blackCanvas;
        useJFileChooser = Prefs.useJFileChooser;
        debugMode = IJ.debugMode;
        foregroundColor =Toolbar.getForegroundColor();
        backgroundColor =Toolbar.getBackgroundColor();
        roiColor = Roi.getColor();
        pointAutoMeasure = Prefs.pointAutoMeasure;
        requireControlKey = Prefs.requireControlKey;
        useInvertingLut = Prefs.useInvertingLut;
        doubleBuffer = Prefs.doubleBuffer;
        saveSettingsCalled = true;
        measurements = Analyzer.getMeasurements();
        decimalPlaces = Analyzer.getPrecision();
        blackBackground = Prefs.blackBackground;
    }
    
    void restoreSettings() {
        interp.getParens();
        if (!saveSettingsCalled)
            interp.error("saveSettings() not called");
        Prefs.usePointerCursor = usePointerCursor;
        IJ.hideProcessStackDialog = hideProcessStackDialog;
        FloatBlitter.divideByZeroValue = divideByZeroValue;
        JpegWriter.setQuality(jpegQuality);
        Line.setWidth(lineWidth);
        ImageConverter.setDoScaling(doScaling);
        if (weightedColor!=Prefs.weightedColor) {
            ColorProcessor.setWeightingFactors(weights[0], weights[1], weights[2]);
            Prefs.weightedColor = !(weights[0]==1d/3d && weights[1]==1d/3d && weights[2]==1d/3d);
        }
        Prefs.interpolateScaledImages = interpolateScaledImages;
        Prefs.open100Percent = open100Percent;
        Prefs.blackCanvas = blackCanvas;
        Prefs.useJFileChooser = useJFileChooser;
        Prefs.useInvertingLut = useInvertingLut;
        Prefs.doubleBuffer = doubleBuffer;
        IJ.debugMode = debugMode;
        Toolbar.setForegroundColor(foregroundColor);
        Toolbar.setBackgroundColor(backgroundColor);
        Roi.setColor(roiColor);
        Analyzer.setMeasurements(measurements);
        Analyzer.setPrecision(decimalPlaces);
        ColorProcessor.setWeightingFactors(weights[0], weights[1], weights[2]);
        Prefs.blackBackground = blackBackground;
    }
    
    void setKeyDown() {
        String keys = getStringArg();
        keys = keys.toLowerCase(Locale.US);
        altKeyDown = keys.indexOf("alt")!=-1;
        if (altKeyDown)
            IJ.setKeyDown(KeyEvent.VK_ALT);
        else
            IJ.setKeyUp(KeyEvent.VK_ALT);
        shiftKeyDown = keys.indexOf("shift")!=-1;
        if (shiftKeyDown)
            IJ.setKeyDown(KeyEvent.VK_SHIFT);
        else
            IJ.setKeyUp(KeyEvent.VK_SHIFT);     
        if (keys.equals("space"))
            IJ.setKeyDown(KeyEvent.VK_SPACE);
        else
            IJ.setKeyUp(KeyEvent.VK_SPACE);
        if (keys.indexOf("esc")!=-1)
            abortPluginOrMacro();
        else
            interp.keysSet = true;
    }
    
    void abortPluginOrMacro() {
        Interpreter.abortPrevious();
        IJ.setKeyDown(KeyEvent.VK_ESCAPE);
        ImagePlus imp = WindowManager.getCurrentImage();
        if (imp!=null) {
            ImageWindow win = imp.getWindow();
            if (win!=null) {
                win.running = false;
                win.running2 = false;
            }
        }
        //Macro.abort();
    }
    
    void open() {
        interp.getLeftParen();
        if (interp.nextToken()==')') {
            interp.getRightParen();
            IJ.open();
        } else {
            String path = getString();
            interp.getRightParen();
            IJ.open(path);
        }
        resetImage();
    }
    
    double roiManager() {
        String cmd = getFirstString();
        cmd = cmd.toLowerCase();
        String path = null;
        int index=0;
        double countOrIndex=Double.NaN;
        boolean twoArgCommand = cmd.equals("open")||cmd.equals("save")||cmd.equals("rename");
        boolean select = cmd.equals("select");
        if (twoArgCommand)
            path = getLastString();
        else if (select)
            index = (int)getLastArg();
        else
            interp.getRightParen();
        if (RoiManager.getInstance()==null&&roiManager==null) {
            if (Interpreter.isBatchMode())
                roiManager = new RoiManager(true);
            else
                IJ.run("ROI Manager...");
        }
        RoiManager rm = roiManager!=null?roiManager:RoiManager.getInstance();
        if (rm==null)
            interp.error("ROI Manager not found");
        if (twoArgCommand)
            rm.runCommand(cmd, path);
        else if (select) {
            int n = rm.getList().getItemCount();
            checkIndex(index, 0, n-1);
            if (shiftKeyDown || altKeyDown) {
                rm.select(index, shiftKeyDown, altKeyDown);
                shiftKeyDown = altKeyDown = false;
            } else
                rm.select(index);
        } else if (cmd.equals("count"))
            countOrIndex = rm.getList().getItemCount();
        else if (cmd.equals("index"))
            countOrIndex = rm.getList().getSelectedIndex();
        else {
            if (!rm.runCommand(cmd))
                interp.error("Invalid ROI Manager command");
        }
        return countOrIndex;            
    }
    
    void setFont() {
        String name = getFirstString();
        int size = (int)getNextArg();
        int style = 0;
        antialiasedText = false;
        if (interp.nextToken()==',') {
            String styles = getLastString().toLowerCase();
            if (styles.indexOf("bold")!=-1) style += Font.BOLD;
            if (styles.indexOf("italic")!=-1) style += Font.ITALIC;
            if (styles.indexOf("anti")!=-1) antialiasedText = true;
        } else
            interp.getRightParen();
        font = new Font(name, style, size);
        fontSet = false;
    }

    void getMinAndMax() {
        Variable min = getFirstVariable();
        Variable max = getLastVariable();
        ImagePlus imp = getImage();
        double v1 = imp.getDisplayRangeMin();
        double v2 = imp.getDisplayRangeMax();
        if (imp.getBitDepth()==16) {
            Calibration cal = imp.getCalibration();
            v1 = cal.getCValue(v1); 
            v2 = cal.getCValue(v2); 
        }
        min.setValue(v1);
        max.setValue(v2);
    }
    
    void selectImage() {
        interp.getLeftParen();
        if (isStringArg()) {
            selectImage(getString());
            interp.getRightParen();
        } else {
            int id = (int)interp.getExpression();
            IJ.selectWindow(id);
            interp.getRightParen();
        }
        resetImage();
    }
    
    void selectImage(String title) {
        if (Interpreter.isBatchMode()) {
            if (Interpreter.imageTable!=null) {
                for (Enumeration en=Interpreter.imageTable.elements(); en.hasMoreElements();) {
                    ImagePlus imp = (ImagePlus)en.nextElement();
                    if (imp!=null) {
                        if (imp.getTitle().equals(title)) {
                            ImagePlus imp2 = WindowManager.getCurrentImage();
                            if (imp2!=null && imp2!=imp) imp2.saveRoi();
                            WindowManager.setTempCurrentImage(imp);
                            return;
                        }
                    }
                }
            }
            selectWindowManagerImage(title);
        } else
            selectWindowManagerImage(title);
    }
    
    void notFound(String title) {
        interp.error(title+" not found");
    }

    void selectWindowManagerImage(String title) {
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis()-start<4000) { // 4 sec timeout
            int[] wList = WindowManager.getIDList();
            int len = wList!=null?wList.length:0;
            for (int i=0; i<len; i++) {
                ImagePlus imp = WindowManager.getImage(wList[i]);
                if (imp!=null) {
                    if (imp.getTitle().equals(title)) {
                        IJ.selectWindow(imp.getID());
                        return;
                    }
                }
            }
            IJ.wait(10);
        }
        notFound(title);
    }

    void close() {
        interp.getParens();
        ImagePlus imp = getImage();
        ImageWindow win = imp.getWindow();
        if (win!=null) {
            imp.changes = false;
            win.close();
        } else {
            imp.saveRoi();
            WindowManager.setTempCurrentImage(null);
            interp.removeBatchModeImage(imp);
        }
        resetImage();
    }
    
    void setBatchMode() {
        boolean enterBatchMode = false;
        String sarg = null;
        interp.getLeftParen();
        if (isStringArg())
            sarg = getString();
        else {
            double arg = interp.getBooleanExpression();
            interp.checkBoolean(arg);
            enterBatchMode = arg==1.0;
        }
        interp.getRightParen();
        if (!interp.isBatchMode())
            interp.calledMacro = false;
        resetImage();
        if (enterBatchMode)  { // true
            if (interp.isBatchMode()) return;
            interp.setBatchMode(true);
            ImagePlus tmp = WindowManager.getTempCurrentImage();
            if (tmp!=null)
                Interpreter.addBatchModeImage(tmp);
            return;
        }
        IJ.showProgress(0, 0);
        ImagePlus imp2 = WindowManager.getCurrentImage();
        WindowManager.setTempCurrentImage(null);
        roiManager = null;
        if (sarg==null) {  //false
            interp.setBatchMode(false);
            displayBatchModeImage(imp2);
        } else {
            Vector v = Interpreter.imageTable;
            if (v==null) return;
            interp.setBatchMode(false);
            for (int i=0; i<v.size(); i++) {
                imp2 = (ImagePlus)v.elementAt(i);
                if (imp2!=null) 
                    displayBatchModeImage(imp2);
            }
        }
    }
    
    void displayBatchModeImage(ImagePlus imp2) {
        if (imp2!=null) {
            ImageWindow win = imp2.getWindow();
            if (win==null)
                imp2.show();
            else {
                if (!win.isVisible()) win.show(); 
                imp2.updateAndDraw();
            }
            Roi roi = imp2.getRoi();
            if (roi!=null) imp2.setRoi(roi);
        }
    }
    
    void setLocation() {
        int x = (int)getFirstArg();
        int y = (int)getNextArg();
        int width=0, height=0;
        if (interp.nextToken()==',') {
            width = (int)getNextArg();
            height = (int)getNextArg();
        }
        interp.getRightParen();
        if (width==0&&height==0) {
            Frame frame = WindowManager.getFrontWindow();
            if (frame!=null)
                frame.setLocation(x, y);
        } else {
            ImagePlus imp = getImage();
            ImageWindow win = imp.getWindow();
            if (win!=null)
                win.setLocationAndSize(x, y, width, height);
        }
    }
    
    void setSlice() {
        int n = (int)getArg();
        ImagePlus imp = getImage();
        int nSlices = imp.getStackSize();
        if (n==1 && nSlices==1)
            return;
        else if (n<1 || n>nSlices)
            interp.error("Argument must be >=1 and <="+nSlices);
        else {
            if (imp.isHyperStack())
                imp.setPosition(n);
            else
                imp.setSlice(n);
        }
        resetImage();
    }
    
    void newImage() {
        String title = getFirstString();
        String type = getNextString();
        int width = (int)getNextArg();
        int height = (int)getNextArg();
        int depth = (int)getLastArg();
        IJ.newImage(title, type, width, height, depth);
        resetImage();
    }

    void saveAs() {
        String format = getFirstString();
        String path =  null;
        if (interp.nextNonEolToken()==',')
            path = getLastString();
        else
            interp.getRightParen();
        IJ.saveAs(format, path);
    }

    double getZoom() {
        interp.getParens();
        ImagePlus imp = getImage();
        ImageCanvas ic = imp.getCanvas();
        if (ic==null)
            {interp.error("Image not displayed"); return 0.0;}
        else
            return ic.getMagnification();
    }
    
    void setAutoThreshold() {
        interp.getParens();
        ImagePlus img = getImage();
        ImageProcessor ip = getProcessor();
        if (ip instanceof ColorProcessor)
            interp.error("Non-RGB image expected");
        ip.setRoi(img.getRoi());
        ip.setAutoThreshold(ImageProcessor.ISODATA2, ImageProcessor.RED_LUT);
        img.updateAndDraw();
        resetImage();
    }
    
    double parseDouble(String s) {
            if (s==null) return 0.0;
            s = s.trim();
            if (s.indexOf(' ')!=-1) s = s.substring(0, s.indexOf(' '));
            return Tools.parseDouble(s);
    }
    
    double parseInt() {
        String s = getFirstString();
        int radix = 10;
        if (interp.nextToken()==',') {
            interp.getComma();
            radix = (int)interp.getExpression();
            if (radix<2||radix>36) radix = 10;
        }
        interp.getRightParen();
        double n;
        try {
            if (radix==10) {
                n = parseDouble(s);
                if (!Double.isNaN(n)) n = Math.round(n);
            } else
                n = Integer.parseInt(s, radix);
        } catch (NumberFormatException e) {
            n = Double.NaN;
        }
        return n;           
    }

    void print() {
        interp.inPrint = true;
        String s = getFirstString();
        if (interp.nextNonEolToken()==',') {
            if (s.startsWith("[") && s.endsWith("]")) {
                printToWindow(s);
                return;
            } else if (s.equals("~0~")) {
                if (writer==null)
                    interp.error("File not open");
                String s2 = getLastString();
                if (s2.endsWith("\n"))
                    writer.print(s2);
                else
                    writer.println(s2);
                interp.inPrint = false;
                return;
            }
            StringBuffer sb = new StringBuffer(s);
            do {
                sb.append(" ");
                sb.append(getNextString());
            } while (interp.nextNonEolToken()==',');
            s = sb.toString();
        }
        interp.getRightParen();
        IJ.log(s);
        interp.inPrint = false;
    }
    
    void printToWindow(String s) {
        String title = s.substring(1, s.length()-1);
        String s2 = getLastString();
        boolean isCommand = s2.startsWith("\\");
        Frame frame = WindowManager.getFrame(title);
        if (frame==null) {
            if (isCommand) {
                interp.done = true;
                return;
            } else
                interp.error("Window not found");
        }
        boolean isEditor = frame instanceof Editor;
        if (!(isEditor || frame instanceof TextWindow))
            interp.error("Window is not text window");
        if (isEditor) {
            Editor ed = (Editor)frame;
            ed.setIsMacroWindow(true);
            if (isCommand)
                handleEditorCommand(ed, s2);
            else
                ed.append(s2);
        } else {
            TextWindow tw = (TextWindow)frame;
            if (isCommand)
                handleTextWindowCommand(tw, s2);
            else
                tw.append(s2);
        }
    }
    
    void handleEditorCommand(Editor ed, String s) {
        if (s.startsWith("\\Update:")) {
            TextArea ta = ed.getTextArea();
            ta.setText(s.substring(8, s.length()));
            ta.setEditable(false);
        } else if (s.equals("\\Close"))
            ed.close();
        else
            ed.append(s);
    }

    void handleTextWindowCommand(TextWindow tw, String s) {
        TextPanel tp = tw.getTextPanel();
        if (s.startsWith("\\Update:")) {
            int n = tp.getLineCount();
            String s2 = s.substring(8, s.length());
            if (n==0)
                tp.append(s2);
            else
                tp.setLine(n-1, s2);
        } else if (s.startsWith("\\Update")) {
            int cindex = s.indexOf(":");
            if (cindex==-1)
                {tp.append(s); return;}
            String nstr = s.substring(7, cindex);
            int line = (int)Tools.parseDouble(nstr, -1);
            if (line<0) interp.error("Row index<0 or NaN");
            int count = tp.getLineCount();
            while (line>=count) {
                tp.append("");
                count++;
            }
            String s2 = s.substring(cindex+1, s.length());
            tp.setLine(line, s2);
        } else if (s.equals("\\Clear"))
            tp.clear();
        else if (s.equals("\\Close"))
            tw.close();
        else if (s.startsWith("\\Headings:"))
            tp.setColumnHeadings(s.substring(10));
        else
            tp.append(s);
    }

    
    double isKeyDown() {
        double value = 0.0;
        String key = getStringArg().toLowerCase(Locale.US);
        if (key.indexOf("alt")!=-1) value = IJ.altKeyDown()==true?1.0:0.0;
        else if (key.indexOf("shift")!=-1) value = IJ.shiftKeyDown()==true?1.0:0.0;
        else if (key.indexOf("space")!=-1) value = IJ.spaceBarDown()==true?1.0:0.0;
        else interp.error("Invalid key");
        return value;
    }

    String runMacro(boolean eval) {
        interp.getLeftParen();
        String name = getString();
        String arg = null;
        if (interp.nextNonEolToken()==',') {
            interp.getComma();
            arg = getString();
        }
        interp.getRightParen();
        if (eval)
            return IJ.runMacro(name, arg);
        else
            return IJ.runMacroFile(name, arg);
    }

    void setThreshold() {
        double lower = getFirstArg();
        double upper = getNextArg();
        String mode = null;
        if (interp.nextNonEolToken()==',') {
            interp.getComma();
            mode = getString();
        }
        interp.getRightParen();
        IJ.setThreshold(lower, upper, mode);
        resetImage();
    }

    void drawOrFill(int type) {
        int x = (int)getFirstArg();
        int y = (int)getNextArg();
        int width = (int)getNextArg();
        int height = (int)getLastArg();
        ImageProcessor ip = getProcessor();
        if (!colorSet) setForegroundColor(ip);
        switch (type) {
            case DRAW_RECT: ip.drawRect(x, y, width, height); break;
            case FILL_RECT: ip.setRoi(x, y, width, height); ip.fill(); break;
            case DRAW_OVAL: ip.drawOval(x, y, width, height); break;
            case FILL_OVAL: ip.fillOval(x, y, width, height); break;
        }
        updateAndDraw(defaultImp);
    }

    double getScreenDimension(int type) {
        interp.getParens();
        Dimension screen = IJ.getScreenSize();
        if (type==SCREEN_WIDTH)
            return screen.width;
        else
            return screen.height;
    }

    void getStatistics(boolean calibrated) {
        Variable count = getFirstVariable();
        Variable mean=null, min=null, max=null, std=null, hist=null;
        int params = AREA+MEAN+MIN_MAX;
        interp.getToken();
        int arg = 1;
        while (interp.token==',') {
            arg++;
            switch (arg) {
                case 2: mean = getVariable(); break;
                case 3: min = getVariable(); break;
                case 4: max = getVariable(); break;
                case 5: std = getVariable(); params += STD_DEV; break;
                case 6: hist = getArrayVariable(); break;
                default: interp.error("')' expected");
            }
            interp.getToken();
        }
        if (interp.token!=')') interp.error("')' expected");
        resetImage();
        ImagePlus imp = getImage();
        Calibration cal = calibrated?imp.getCalibration():null;
        ImageProcessor ip = getProcessor();
        ImageStatistics stats = null;
        Roi roi = imp.getRoi();
        if (roi!=null && roi.isLine()) {
            ProfilePlot profile = new ProfilePlot(imp);
            double[] values = profile.getProfile();
            ImageProcessor ip2 = new FloatProcessor(values.length, 1, values);
            if (roi instanceof Line) {
                Line l = (Line)roi;
                if ((l.y1==l.y2||l.x1==l.x2)&&l.x1==l.x1d&& l.y1==l.y1d&& l.x2==l.x2d&& l.y2==l.y2d)
                    ip2.setRoi(0, 0, ip2.getWidth()-1, 1);
            }
            stats = ImageStatistics.getStatistics(ip2, params, cal);
        } else {
            ip.setRoi(roi);
            stats = ImageStatistics.getStatistics(ip, params, cal);
        }
        if (calibrated)
            count.setValue(stats.area);
        else
            count.setValue(stats.pixelCount);
        if (mean!=null) mean.setValue(stats.mean);
        if (min!=null) min.setValue(stats.min);
        if (max!=null) max.setValue(stats.max);
        if (std!=null) std.setValue(stats.stdDev);
        if (hist!=null) {
            boolean is16bit = !calibrated && ip instanceof ShortProcessor;
            int[] histogram = is16bit?stats.histogram16:stats.histogram;
            int bins = is16bit?(int)(stats.max+1):histogram.length;
            Variable[] array = new Variable[bins];
            int hmax = is16bit?(int)stats.max:255;
            for (int i=0; i<=hmax; i++)
                array[i] = new Variable(histogram[i]);
            hist.setArray(array);
        }
    }
    
    String replace() {
        String s1 = getFirstString();
        String s2 = getNextString();
        String s3 = getLastString();
        if (s2.length()==1 && s3.length()==1)
            return s1.replace(s2.charAt(0), s3.charAt(0));
        else
            return s1.replaceAll(s2, s3);
    }
    
    void floodFill() {
        int x = (int)getFirstArg();
        int y = (int)getNextArg();
        boolean fourConnected = true;
        if (interp.nextNonEolToken()==',') {
            String s = getLastString();
            if (s.indexOf("8")!=-1)
                fourConnected = false;
        } else
            interp.getRightParen();
        ImageProcessor ip = getProcessor();
        if (!colorSet) setForegroundColor(ip);
        FloodFiller ff = new FloodFiller(ip);
        if (fourConnected)
            ff.fill(x, y);
        else
            ff.fill8(x, y);
        updateAndDraw(defaultImp);
        if (Recorder.record && pgm.hasVars)
            Recorder.record("floodFill", x, y);
    }
    
    void restorePreviousTool() {
        interp.getParens();
        Toolbar tb = Toolbar.getInstance();
        if (tb!=null) tb.restorePreviousTool();
    }
    
    void setVoxelSize() {
        double width = getFirstArg();
        double height = getNextArg();
        double depth = getNextArg();
        String unit = getLastString();
        resetImage();
        ImagePlus imp = getImage();
        Calibration cal = imp.getCalibration();
        cal.pixelWidth = width;
        cal.pixelHeight = height;
        cal.pixelDepth = depth;
        cal.setUnit(unit);
        imp.repaintWindow();
    }

    void getLocationAndSize() {
        Variable v1 = getFirstVariable();
        Variable v2 = getNextVariable();
        Variable v3 = getNextVariable();
        Variable v4 = getLastVariable();
        ImagePlus imp = getImage();
        int x=0, y=0, w=0, h=0;
        ImageWindow win = imp.getWindow();
        if (win!=null) {
            Point loc = win.getLocation();
            Dimension size = win.getSize();
            x=loc.x; y=loc.y; w=size.width; h=size.height;
        }
        v1.setValue(x);
        v2.setValue(y);
        v3.setValue(w);
        v4.setValue(h);
    }
    
    String doDialog() {
        interp.getToken();
        if (interp.token!='.')
            interp.error("'.' expected");
        interp.getToken();
        if (!(interp.token==WORD || interp.token==STRING_FUNCTION || interp.token==NUMERIC_FUNCTION))
            interp.error("Function name expected: ");
        String name = interp.tokenString;
        try {
            if (name.equals("create")) {
                gd = new GenericDialog(getStringArg());
                return null;
            }
            if (gd==null) {
                interp.error("No dialog created with Dialog.create()"); 
                return null;
            }
            if (name.equals("addString")) {
                String label = getFirstString();
                String defaultStr = getNextString();
                int columns = 8;
                if (interp.nextNonEolToken()==',')
                    columns = (int)getNextArg();
                interp.getRightParen();
                gd.addStringField(label, defaultStr, columns);
            } else if (name.equals("addNumber")) {
                int columns = 6;
                String units = null;
                String prompt = getFirstString();
                double defaultNumber = getNextArg();
                int decimalPlaces = (int)defaultNumber==defaultNumber?0:3;
                if (interp.nextNonEolToken()==',') {
                    decimalPlaces = (int)getNextArg();
                    columns = (int)getNextArg();
                    units = getLastString();
                } else
                    interp.getRightParen();
                gd.addNumericField(prompt, defaultNumber, decimalPlaces, columns, units);
            } else if (name.equals("addCheckbox")) {
                gd.addCheckbox(getFirstString(), getLastArg()==1?true:false);
            } else if (name.equals("addCheckboxGroup")) {
                addCheckboxGroup(gd);
            } else if (name.equals("addMessage")) {
                gd.addMessage(getStringArg());
            } else if (name.equals("addChoice")) {
                String prompt = getFirstString();
                interp.getComma();
                String[] choices = getStringArray();
                String defaultChoice = null;
                if (interp.nextNonEolToken()==',') {
                    interp.getComma();
                    defaultChoice = getString();
                } else
                    defaultChoice = choices[0];
                interp.getRightParen();
                gd.addChoice(prompt, choices, defaultChoice);
            } else if (name.equals("show")) {
                interp.getParens();
                gd.showDialog();
                if (gd.wasCanceled()) {
                    interp.finishUp();
                    throw new RuntimeException(Macro.MACRO_CANCELED);
                }
            } else if (name.equals("getString")) {
                interp.getParens();
                return gd.getNextString();
            } else if (name.equals("getNumber")) {
                interp.getParens();
                return ""+gd.getNextNumber();
            } else if (name.equals("getCheckbox")) {
                interp.getParens();
                return gd.getNextBoolean()==true?"1":"0";
            } else if (name.equals("getChoice")) {
                interp.getParens();
                return gd.getNextChoice();
            } else
                interp.error("Unrecognized Dialog function "+name);
        } catch (IndexOutOfBoundsException e) {
            interp.error("Dialog error");
        }
        return null;
    }
    
    void addCheckboxGroup(GenericDialog gd) {
        int rows = (int)getFirstArg();
        int columns = (int)getNextArg();
        interp.getComma();
        String[] labels = getStringArray();
        int n = labels.length;
        double[] dstates = getLastArray();
        if (n!=dstates.length)
            interp.error("labels.length!=states.length");
        boolean[] states = new boolean[n];
        for (int i=0; i<n; i++)
            states[i] = dstates[i]==1.0?true:false;
        gd.addCheckboxGroup(rows, columns, labels, states);
    }

    void getDateAndTime() {
        Variable year = getFirstVariable();
        Variable month = getNextVariable();
        Variable dayOfWeek = getNextVariable();
        Variable dayOfMonth = getNextVariable();
        Variable hour = getNextVariable();
        Variable minute = getNextVariable();
        Variable second = getNextVariable();
        Variable millisecond = getLastVariable();
        Calendar date = Calendar.getInstance();
        year.setValue(date.get(Calendar.YEAR));
        month.setValue(date.get(Calendar.MONTH));
        dayOfWeek.setValue(date.get(Calendar.DAY_OF_WEEK)-1);
        dayOfMonth.setValue(date.get(Calendar.DAY_OF_MONTH));
        hour.setValue(date.get(Calendar.HOUR_OF_DAY));
        minute.setValue(date.get(Calendar.MINUTE));
        second.setValue(date.get(Calendar.SECOND));
        millisecond.setValue(date.get(Calendar.MILLISECOND));
    }
    
    void setMetadata() {
        String metadata = null;
        String arg1 = getFirstString();
        boolean oneArg = false;
        if (interp.nextNonEolToken()==',')
            metadata = getLastString();
        else
            interp.getRightParen();
        boolean isInfo = false;
        if (metadata==null) {
            metadata = arg1;
            oneArg = true;
            if (metadata.startsWith("Info:")) {
                metadata = metadata.substring(5);
                isInfo = true;
            }
        } else
            isInfo = arg1.startsWith("info") || arg1.startsWith("Info");
        ImagePlus imp = getImage();
        if (isInfo)
            imp.setProperty("Info", metadata);
        else {
            if (imp.getStackSize()==1) {
                if (oneArg)
                    imp.setProperty("Info", metadata);
                else {
                    imp.setProperty("Label", metadata);
                    if (!Interpreter.isBatchMode()) imp.repaintWindow();
                }
            } else {
                imp.getStack().setSliceLabel(metadata, imp.getCurrentSlice());
                if (!Interpreter.isBatchMode()) imp.repaintWindow();
            }
        }
    }

    String getMetadata() {
        String type = "label";
        boolean noArg = true;
        if (interp.nextNonEolToken()=='(' && interp.nextNextNonEolToken()!=')') {
            type = getStringArg().toLowerCase(Locale.US);
            noArg = false;
        } else
            interp.getParens();
        ImagePlus imp = getImage();
        String metadata = null;
        if (type.indexOf("label")!=-1) {
            if (imp.getStackSize()==1) {
                metadata = (String)imp.getProperty("Label");
                if (metadata==null && noArg)
                    metadata = (String)imp.getProperty("Info");
            } else 
                metadata = imp.getStack().getSliceLabel(imp.getCurrentSlice());
        } else
            metadata = (String)imp.getProperty("Info");
        if (metadata==null) metadata = "";
        return metadata;
    }

    ImagePlus getImageArg() {
        ImagePlus img = null;
        if (isStringArg()) {
            String title = getString();
            img = WindowManager.getImage(title);
        } else {
            int id = (int)interp.getExpression();
            img = WindowManager.getImage(id);
        }
        if (img==null) interp.error("Image not found");
        return img;
    }

    void imageCalculator() {
        String operator = getFirstString();
        interp.getComma();
        ImagePlus img1 = getImageArg();
        interp.getComma();
        ImagePlus img2 = getImageArg();
        interp.getRightParen();
        ImageCalculator ic = new ImageCalculator();
        ic.calculate(operator, img1, img2);
        resetImage();
    }

    void setRGBWeights() {
        double r = getFirstArg();
        double g = getNextArg();
        double b = getLastArg();
        if (interp.rgbWeights==null)
            interp.rgbWeights = ColorProcessor.getWeightingFactors();
        ColorProcessor.setWeightingFactors(r, g, b);
    }

    void makePolygon() {
        int max = 200;
        int[] x = new int[max];
        int[] y = new int[max];
        x[0] = (int)Math.round(getFirstArg());
        y[0] = (int)Math.round(getNextArg());
        interp.getToken();
        int n = 1;
        while (interp.token==',' && n<max) {
            x[n] = (int)Math.round(interp.getExpression());
            interp.getComma();
            y[n] = (int)Math.round(interp.getExpression());
            interp.getToken();
            n++;
        }
        if (n<3)
            interp.error("Fewer than 3 points");
        if (n==max && interp.token!=')')
            interp.error("More than "+max+" points");
        ImagePlus imp = getImage();
        Roi previousRoi = imp.getRoi();
        if (shiftKeyDown||altKeyDown) imp.saveRoi();
        imp.setRoi(new PolygonRoi(x, y, n, Roi.POLYGON));
        Roi roi = imp.getRoi();
        if (previousRoi!=null && roi!=null)
            updateRoi(roi); 
        resetImage(); 
    }
    
    void updateRoi(Roi roi) {
        if (shiftKeyDown || altKeyDown)
            roi.update(shiftKeyDown, altKeyDown);
        shiftKeyDown = altKeyDown = false;
    }
    
    String doFile() {
        interp.getToken();
        if (interp.token!='.')
            interp.error("'.' expected");
        interp.getToken();
        if (!(interp.token==WORD || interp.token==STRING_FUNCTION || interp.token==NUMERIC_FUNCTION || interp.token==PREDEFINED_FUNCTION))
            interp.error("Function name expected: ");
        String name = interp.tokenString;
        if (name.equals("open"))
            return openFile();
        else if (name.equals("openAsString"))
            return openAsString(false);
        else if (name.equals("openAsRawString"))
            return openAsString(true);
        else if (name.equals("openUrlAsString"))
            return openUrlAsString();
        else if (name.equals("openDialog"))
            return openDialog();
        else if (name.equals("close"))
            return closeFile();
        else if (name.equals("separator")) {
            interp.getParens();
            return File.separator;
        } else if (name.equals("directory")) {
            interp.getParens();
            String lastDir = OpenDialog.getLastDirectory();
            return lastDir!=null?lastDir:"";
        } else if (name.equals("name")) {
            interp.getParens();
            String lastName = OpenDialog.getLastName();
            return lastName!=null?lastName:"";
        } else if (name.equals("rename")) {
            File f1 = new File(getFirstString());
            File f2 = new File(getLastString());
            if (checkPath(f1) && checkPath(f2)) 
                return f1.renameTo(f2)?"1":"0";
            else
                return "0";
        } else if (name.equals("append")) {
            String err = IJ.append(getFirstString(), getLastString());
            if (err!=null) interp.error(err);
            return null;
        } else if (name.equals("saveString")) {
            String err = IJ.saveString(getFirstString(), getLastString());
            if (err!=null) interp.error(err);
            return null;
        }
        File f = new File(getStringArg());
        if (name.equals("getLength")||name.equals("length"))
            return ""+f.length();
        else if (name.equals("getName"))
            return f.getName();
        else if (name.equals("getAbsolutePath"))
            return f.getAbsolutePath();
        else if (name.equals("getParent"))
            return f.getParent();
        else if (name.equals("exists"))
            return f.exists()?"1":"0";
        else if (name.equals("isDirectory"))
            return f.isDirectory()?"1":"0";
        else if (name.equals("makeDirectory")||name.equals("mkdir")) {
            f.mkdir(); return null;
        } else if (name.equals("lastModified"))
            return ""+f.lastModified();
        else if (name.equals("dateLastModified"))
            return (new Date(f.lastModified())).toString();
        else if (name.equals("delete")) {
            String ok = null;
            if (isValid(f)) ok=f.delete()?"1":"0";
            return ok;
        } else
            interp.error("Unrecognized File function "+name);
        return null;
    }
    
    boolean isValid(File f) {
        String path = f.getPath();
        if (path.equals("0") || path.equals("NaN") )
                interp.error("Invalid path");
        path = f.getAbsolutePath();
        if (!(path.indexOf("ImageJ")!=-1
        ||path.startsWith(System.getProperty("java.io.tmpdir"))
        ||path.startsWith(System.getProperty("user.home")))) {
            interp.error("File must be in ImageJ, home or tmp directory");
            return false;
        } else
            return true;
    }
    
    boolean checkPath(File f) {
        String path = f.getPath();
        if (path.equals("0") || path.equals("NaN")) {
                interp.error("Invalid path");
                return false;
        } else
            return true;
    }

    String openDialog() {
        String title = getStringArg();
        OpenDialog od = new OpenDialog(title, null);
        String directory = od.getDirectory();
        String name = od.getFileName();
        if (name==null)
            return "";
        else
            return directory+name;
    }

    void setSelectionName() {
        Roi roi = getImage().getRoi();
        if (roi==null)
            interp.error("No selection");
        else
            roi.setName(getStringArg());
    }

    String selectionName() {
        Roi roi = getImage().getRoi();
        String name = null;
        if (roi==null)
            interp.error("No selection");
        else
            name = roi.getName();
        return name!=null?name:"";
    }
    
    String openFile() {
        if (writer!=null) {
            interp.error("Currently, only one file can be open at a time");
            return"";
        }
        String path = getFirstString();
        String defaultName = null;
        if (interp.nextToken()==')')
            interp.getRightParen();
        else
            defaultName = getLastString();
        if (path.equals("") || defaultName!=null) {
            String title = defaultName!=null?path:"openFile";
            defaultName = defaultName!=null?defaultName:"log.txt";
            SaveDialog sd = new SaveDialog(title, defaultName, ".txt");
            if(sd.getFileName()==null) return "";
            path = sd.getDirectory()+sd.getFileName();
        } else {
            File file = new File(path);
            if (file.exists() && !(path.endsWith(".txt")||path.endsWith(".java")||path.endsWith(".xls")||path.endsWith(".ijm")))
                interp.error("File exists and suffix is not '.txt', '.java', etc.");
        }
        try {
            FileOutputStream fos = new FileOutputStream(path);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            writer = new PrintWriter(bos);
        }
        catch (IOException e) {
            interp.error("File open error \n\""+e.getMessage()+"\"\n");
            return "";
        }
        return "~0~";
    }

    String openAsString(boolean raw) {
        int max = 100000;
        String path = getFirstString();
        if (raw && interp.nextToken()==',')
            max = (int)getNextArg();
        interp.getRightParen();         
        if (path.equals("")) {
            OpenDialog od = new OpenDialog("OpenAsString...", "");
            String directory = od.getDirectory();
            String name = od.getFileName();
            if (name==null) return "";
            path = directory + name;
        }
        String str = "";
        File file = new File(path);
        if (!file.exists())
            interp.error("File not found");
        try {
            StringBuffer sb = new StringBuffer(5000);
            BufferedReader r = new BufferedReader(new FileReader(file));
            if (raw) {
                while (sb.length()<max) {
                    int c=r.read();
                    if (c==-1)
                        break;
                    else
                        sb.append((char)c);
                }
            } else {
                while (true) {
                    String s=r.readLine();
                    if (s==null)
                        break;
                    else
                        sb.append(s+"\n");
                }
            }
            r.close();
            str = new String(sb);
        }
        catch (Exception e) {
            interp.error("File open error \n\""+e.getMessage()+"\"\n");
        }
        return str;
    }
    
    String closeFile() {
        String f = getStringArg();
        if (!f.equals("~0~"))
            interp.error("Invalid file variable");
        if (writer!=null) {
            writer.close();
            writer = null;
        }
        return null;
    }
    
    // Calls a public static method with an arbitrary number
    // of String parameters, returning a String.
    // Contributed by Johannes Schindelin
    String call() {
        // get class and method name
        String fullName = getFirstString();
        int dot = fullName.lastIndexOf('.');
        if(dot<0)
            interp.error("Expected 'classname.methodname'");
        String className = fullName.substring(0,dot);
        String methodName = fullName.substring(dot+1);

        // get optional string arguments
        Object[] args = null;
        if (interp.nextNonEolToken()==',') {
            Vector vargs = new Vector();
            do
                vargs.add(getNextString());
            while (interp.nextNonEolToken()==',');
            args = vargs.toArray();
        }
        interp.getRightParen();
        if (args==null) args = new Object[0];

        // get the class
        Class c;
        try {
            c = IJ.getClassLoader().loadClass(className);
        } catch(Exception ex) {
            interp.error("Could not load class "+className);
            return null;
        }

        // get method
        Method m;
        try {
            Class[] argClasses = null;
            if(args.length>0) {
                argClasses = new Class[args.length];
                for(int i=0;i<args.length;i++)
                    argClasses[i] = args[i].getClass();
            }
            m = c.getMethod(methodName,argClasses);
        } catch(Exception ex) {
            interp.error("Could not find the method "+methodName+" with "+
                     args.length+" parameter(s) in class "+className);
            return null;
        }

        try {
            Object obj = m.invoke(null, args);
            return obj!=null?obj.toString():null;
        } catch(InvocationTargetException e) {
            CharArrayWriter caw = new CharArrayWriter();
            PrintWriter pw = new PrintWriter(caw);
            e.getCause().printStackTrace(pw);
            String s = caw.toString();
            if (IJ.getInstance()!=null)
                new TextWindow("Exception", s, 400, 400);
            else
                IJ.log(s);
            return null;
        } catch(Exception e) {
            IJ.log("Call error ("+e+")");
            return null;
        } 
            
    }
    
    Variable[] getFontList() {
        interp.getParens();
        String fonts[] = null;
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        fonts = ge.getAvailableFontFamilyNames();
        if (fonts==null) return null;
        Variable[] array = new Variable[fonts.length];
        for (int i=0; i<fonts.length; i++)
            array[i] = new Variable(0, 0.0, fonts[i]);
        return array;
    }
    
    String openUrlAsString() {
        String urlString = getStringArg();
        StringBuffer sb = null;
        try {
            URL url = new URL(urlString);
            InputStream in = url.openStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            sb = new StringBuffer() ;
            String line;
            while ((line=br.readLine()) != null)
                sb.append (line + "\n");
            in.close ();
        } catch (Exception e) {
            return("<Error: "+e+">");
        }
        if (sb!=null)
            return new String(sb);
        else
            return null;
    }
    
    void setOption() {
        String arg1 = getFirstString();
        boolean state = true;
        if (interp.nextToken()==',') {
            interp.getComma();
            double arg2 = interp.getBooleanExpression();
            interp.checkBoolean(arg2);
            state = arg2==0?false:true;
        }
        interp.getRightParen();
        arg1 = arg1.toLowerCase(Locale.US);
        if (arg1.equals("disablepopupmenu")) {
            ImageCanvas ic = getImage().getCanvas();
            if (ic!=null) ic.disablePopupMenu(state);
        } else if (arg1.equals("show all")) {
            ImagePlus img = getImage();
            ImageCanvas ic = img.getCanvas();
            if (ic!=null) {
                boolean previousState = ic.getShowAllROIs();
                ic.setShowAllROIs(state);
                if (state!=previousState) img.draw();
            }
        } else if (arg1.equals("changes"))
            getImage().changes = state;
        else if (arg1.equals("debugmode"))
            IJ.debugMode = state;
        else if (arg1.equals("openusingplugins"))
            Opener.setOpenUsingPlugins(state);
        else if (arg1.equals("queuemacros"))
            pgm.queueCommands = state;
        else if (arg1.equals("disableundo"))
            Prefs.disableUndo = state;
        else if (arg1.startsWith("openashyper"))
            getImage().setOpenAsHyperStack(true);
        else if (arg1.startsWith("display lab"))
            Analyzer.setMeasurement(LABELS, state);
        else if (arg1.startsWith("limit to"))
            Analyzer.setMeasurement(LIMIT, state);
        else if (arg1.equals("area"))
            Analyzer.setMeasurement(AREA, state);
        else if (arg1.equals("mean"))
            Analyzer.setMeasurement(MEAN, state);
        else if (arg1.startsWith("std"))
            Analyzer.setMeasurement(STD_DEV, state);
        else if (arg1.equals("unitispixel"))
            Prefs.unitIsPixel = state;
        else
            interp.error("Invalid option");
    }
    
    void setMeasurementOption(String option) {
    }
    
    void showText() {
        String title = getFirstString();
        String text = getLastString();
        Editor ed = new Editor();
        ed.setSize(350, 300);
        ed.create(title, text);
    }
    
    Variable[] newMenu() {
        String name = getFirstString();
        interp.getComma();
        String[] commands = getStringArray();
        interp.getRightParen();
        if (pgm.menus==null)
            pgm.menus = new Hashtable();
        pgm.menus.put(name, commands);
        Variable[] commands2 = new Variable[commands.length];
        for (int i=0; i<commands.length; i++)
            commands2[i] = new Variable(0, 0.0, commands[i]);
        return commands2;
    }
    
    void setSelectionLocation() {
        int x = (int)Math.round(getFirstArg());
        int y = (int)Math.round(getLastArg());
        resetImage();
        ImagePlus imp = getImage();
        Roi roi = imp.getRoi();
        if (roi==null)
            interp.error("Selection required");
        roi.setLocation(x, y);
        imp.draw();
    }
    
    double is() {
        boolean state = false;
        String arg = getStringArg();
        arg = arg.toLowerCase(Locale.US);
        if (arg.equals("locked"))
            state = getImage().isLocked();
        else if (arg.indexOf("invert")!=-1)
            state = getImage().isInvertedLut();
        else if (arg.indexOf("hyper")!=-1)
            state = getImage().isHyperStack();
        else if (arg.indexOf("batch")!=-1)
            state = Interpreter.isBatchMode();
        else if (arg.indexOf("applet")!=-1)
            state = IJ.getApplet()!=null;
        else if (arg.indexOf("virtual")!=-1)
            state = getImage().getStack().isVirtual();
        else if (arg.indexOf("composite")!=-1)
            state = getImage().isComposite();
        else if (arg.equals("unitispixel"))
            state = Prefs.unitIsPixel;
        else
            interp.error("Argument must be 'locked', 'Inverted LUT' or 'Hyperstack'");
        return state?1.0:0.0;
    }

    Variable[] getList() {
        String key = getStringArg();
        if (key.equals("java.properties")) {
            Properties props = System.getProperties();
            Vector v = new Vector();
            for (Enumeration en=props.keys(); en.hasMoreElements();)
                v.addElement((String)en.nextElement());
            Variable[] array = new Variable[v.size()];
            for (int i=0; i<array.length; i++)
                array[i] = new Variable(0, 0.0, (String)v.elementAt(i));
            return array;
        } else if (key.equals("window.titles")) {
            Frame[] list = WindowManager.getNonImageWindows();
            Variable[] array = new Variable[list.length];
            for (int i=0; i<list.length; i++) {
                Frame frame = list[i];
                array[i] = new Variable(0, 0.0, frame.getTitle());
            }
            return array;
        } else {
            interp.error("Unvalid key");
            return null;
        }
    }
    
    String doString() {
        interp.getToken();
        if (interp.token!='.')
            interp.error("'.' expected");
        interp.getToken();
        if (interp.token!=WORD)
            interp.error("Function name expected: ");
        String name = interp.tokenString;
        if (name.equals("append"))
            return appendToBuffer();
        else if (name.equals("copy"))
            return copyStringToClipboard();
        else if (name.equals("copyResults"))
            return copyResults();
        else if (name.equals("paste"))
            return getClipboardContents();
        else if (name.equals("resetBuffer"))
            return resetBuffer();
        else if (name.equals("buffer"))
            return getBuffer();
        else
            interp.error("Unrecognized String function");
        return null;
    }
    
    String appendToBuffer() {
        String text = getStringArg();
        if (buffer==null)
            buffer = new StringBuffer(256);
        buffer.append(text);
        return null;
    }

    String copyStringToClipboard() {
        String text = getStringArg();
        StringSelection ss = new StringSelection(text);
        java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(ss, null);
        return null;
    }
          
    String getClipboardContents() {
        interp.getParens();
        java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable data = clipboard.getContents(null);
        String s = null;
        try {s = (String)data.getTransferData(DataFlavor.stringFlavor);} 
        catch (Exception e) {s = data.toString();}
        return s;
    }
        
    String copyResults() {
        interp.getParens();
        if (!IJ.isResultsWindow())
            interp.error("No results");
        TextPanel tp = IJ.getTextPanel();
        StringSelection ss = new StringSelection(tp.getText());
        java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(ss, null);
        return null;
    }

    String resetBuffer() {
        interp.getParens();
        buffer = new StringBuffer(256);
        return null;
    }

    String getBuffer() {
        interp.getParens();
        if (buffer==null)
            buffer = new StringBuffer(256);
        return buffer.toString();
    }
    
    void doCommand() {
        String arg = getStringArg();
        if (arg.equals("Start Animation"))
            arg = "Start Animation [\\]";
        IJ.doCommand(arg);
    }
    
    void getDimensions() {
        Variable width = getFirstVariable();
        Variable height = getNextVariable();
        Variable channels = getNextVariable();
        Variable slices = getNextVariable();
        Variable frames = getLastVariable();
        ImagePlus imp = getImage();
        int[] dim = imp.getDimensions();
        width.setValue(dim[0]);
        height.setValue(dim[1]);
        channels.setValue(dim[2]);
        slices.setValue(dim[3]);
        frames.setValue(dim[4]);
    }

    public static void registerExtensions(MacroExtension extensions) {
        Interpreter interp = Interpreter.getInstance();
        if (interp==null) {
            IJ.error("Macro must be running to install macro extensions");
            return;
        }
        interp.pgm.extensionRegistry = new Hashtable();
        ExtensionDescriptor[] descriptors = extensions.getExtensionFunctions();
        for (int i=0; i<descriptors.length; ++i)
            interp.pgm.extensionRegistry.put(descriptors[i].name, descriptors[i]);
    }
    
    String doExt() {
        interp.getToken();
        if (interp.token!='.')
            interp.error("'.' expected");
        interp.getToken();
        if (!(interp.token==WORD || interp.token==STRING_FUNCTION || interp.token==NUMERIC_FUNCTION || interp.token==PREDEFINED_FUNCTION))
            interp.error("Function name expected: ");
        String name = interp.tokenString;
        if (name.equals("install")) {
            Object plugin = IJ.runPlugIn(getStringArg(), "");
            if (plugin==null) interp.error("Plugin not found");
            return null;
        }
        ExtensionDescriptor desc = null;
        if (pgm.extensionRegistry!=null)
            desc = (ExtensionDescriptor) pgm.extensionRegistry.get(name);
        if (desc == null) {
            interp.error("Unrecognized Ext function");
            return null;
        }
        return desc.dispatch(this);
    }
    
    String exec() {
        String[] cmd;
        StringBuffer sb = new StringBuffer(256);
        String arg1 = getFirstString();
        if (interp.nextNonEolToken()==',') {
            Vector v = new Vector();
            v.add(arg1);
            do
                v.add(getNextString());
            while (interp.nextNonEolToken()==',');
            cmd = new String[v.size()];
            v.copyInto((String[])cmd);
        } else
            cmd = Tools.split(arg1);
        interp.getRightParen();
        boolean openingDoc = cmd.length==2&&cmd[0].equals("open") || cmd.length==5&&cmd[3].equals("excel.exe");
        if (openingDoc&&IJ.isWindows()) {
            String path = cmd[1];
            if (path.startsWith("http://")||path.startsWith("HTTP://")) {
                cmd = new String[4];
                cmd[2] = "start";
                cmd[3] = path;
            } else {
                cmd = new String[3];
                cmd[2] = path;
            }
            cmd[0] = "cmd";
            cmd[1] = "/c";
        }
        BufferedReader reader = null;
        try {                                                                                             
            Process p = Runtime.getRuntime().exec(cmd);   
            if (openingDoc) return null;
            reader = new BufferedReader(new InputStreamReader(p.getInputStream()));  
            String line; int count=1;                                                                                
            while ((line=reader.readLine())!=null)  {                                              
                sb.append(line+"\n");
                if (count++==1&&line.startsWith("Microsoft Windows"))
                    break; // user probably ran 'cmd' without /c option
            }                                                             
        } catch (Exception e) {                                                                         
            sb.append(e.getMessage()+"\n");                                                                          
        } finally {
            if (reader!=null) try {reader.close();} catch (IOException e) {}
        }
        return sb.toString(); 
    }

    double getValue() {
        String key = getStringArg();
        if (key.indexOf("foreground")!=-1)
            return Toolbar.getForegroundColor().getRGB()&0xffffff;
        else if (key.indexOf("background")!=-1)
            return Toolbar.getBackgroundColor().getRGB()&0xffffff;
        else {
            interp.error("Invalid key");
            return 0.0;
        }
    }

    double doStack() {
        interp.getToken();
        if (interp.token!='.')
            interp.error("'.' expected");
        interp.getToken();
        if (interp.token!=WORD && interp.token!=PREDEFINED_FUNCTION)
            interp.error("Function name expected: ");
        String name = interp.tokenString;
        if (name.equals("isHyperstack")||name.equals("isHyperStack"))
            return getImage().isHyperStack()?1.0:0.0;
        else if (name.equals("getDimensions"))
            {getDimensions(); return Double.NaN;}
        ImagePlus imp = getImage();
        if (name.equals("setPosition"))
            {setPosition(imp); return Double.NaN;}
        if (name.equals("getPosition"))
            {getPosition(imp); return Double.NaN;}
        if (name.equals("getFrameRate"))
            {interp.getParens(); return imp.getCalibration().fps;}
        if (name.equals("setFrameRate"))
            {imp.getCalibration().fps=getArg(); return Double.NaN;}
        if (imp.getStackSize()==1)
            interp.error("Stack required");
        if (name.equals("setDimensions"))
            setDimensions(imp);
        else if (name.equals("setChannel"))
            imp.setPosition((int)getArg(), imp.getSlice(), imp.getFrame());
        else if (name.equals("setSlice"))
            imp.setPosition(imp.getChannel(), (int)getArg(), imp.getFrame());
        else if (name.equals("setFrame"))
            imp.setPosition(imp.getChannel(), imp.getSlice(), (int)getArg());
        else if (name.equals("setDisplayMode"))
            setDisplayMode(imp, getStringArg());
        else if (name.equals("getDisplayMode"))
            getDisplayMode(imp);
        else if (name.equals("setActiveChannels"))
            setActiveChannels(imp, getStringArg());
        else if (name.equals("swap"))
            swapStackImages(imp);
        else
            interp.error("Unrecognized Stack function");
        return Double.NaN;
    }
    
    void setActiveChannels(ImagePlus imp, String channels) {
        if (!imp.isComposite())
            interp.error("Composite image required");
        boolean[] active = ((CompositeImage)imp).getActiveChannels();
        for (int i=0; i<CompositeImage.MAX_CHANNELS; i++) {
            boolean b = false;
            if (channels.length()>i && channels.charAt(i)=='1')
                b = true;
            active[i] = b;
        }
        imp.updateAndDraw();
    }

    void setDisplayMode(ImagePlus imp, String mode) {
        mode = mode.toLowerCase(Locale.US);
        if (!imp.isComposite())
            interp.error("Composite image required");
        int m = -1;
        if (mode.equals("composite"))
            m = CompositeImage.COMPOSITE;
        else if (mode.equals("color"))
            m = CompositeImage.COLOR;
        else if (mode.startsWith("gray"))
            m = CompositeImage.GRAYSCALE;
        if (m==-1) 
            interp.error("Invalid mode");
        ((CompositeImage)imp).setMode(m);
        imp.updateAndDraw();
    }
    
    void swapStackImages(ImagePlus imp) {
        int n1 = (int)getFirstArg();
        int n2 = (int)getLastArg();
        ImageStack stack = imp.getStack();
        int size = stack.getSize();
        if (n1<1||n1>size||n2<1||n2>size)
            interp.error("Argument out of range");
        Object pixels = stack.getPixels(n1);
        String label = stack.getSliceLabel(n1);
        stack.setPixels(stack.getPixels(n2), n1);
        stack.setSliceLabel(stack.getSliceLabel(n2), n1);
        stack.setPixels(pixels, n2);
        stack.setSliceLabel(label, n2);
        int current = imp.getCurrentSlice();
        if (imp.isComposite()) {
            CompositeImage ci = (CompositeImage)imp;
            if (ci.getMode()==CompositeImage.COMPOSITE) {
                ci.reset();
                imp.updateAndDraw();
                imp.repaintWindow();
                return;
            }
        }
        if (n1==current || n2==current)
            imp.setStack(null, stack);
    }

    void getDisplayMode(ImagePlus imp) {
        Variable v = getVariableArg();
        String mode = "";
        if (imp.isComposite())
            mode = ((CompositeImage)imp).getModeAsString();
        v.setString(mode);
    }

    void getPosition(ImagePlus imp) {
        Variable channel = getFirstVariable();
        Variable slice = getNextVariable();
        Variable frame = getLastVariable();
        int c = imp.getChannel();
        int z = imp.getSlice();
        int t = imp.getFrame();
        if (c*z*t>imp.getStackSize())
            {c=1; z=imp.getCurrentSlice(); t=1;}
        channel.setValue(c);
        slice.setValue(z);
        frame.setValue(t);
    }

    void setPosition(ImagePlus img) {
        int channel = (int)getFirstArg();
        int slice = (int)getNextArg();
        int frame = (int)getLastArg();
        img.setPosition(channel, slice, frame);
    }

    void setDimensions(ImagePlus img) {
        int c = (int)getFirstArg();
        int z = (int)getNextArg();
        int t = (int)getLastArg();
        img.setDimensions(c, z, t);
        if (img.getWindow()==null) img.setOpenAsHyperStack(true);
    }
    
    void setTool() {
        interp.getLeftParen();
        if (isStringArg()) {
            boolean ok = IJ.setTool(getString());
            if (!ok) interp.error("Unrecognized tool name");
        } else
            IJ.setTool((int)interp.getExpression());
        interp.getRightParen();
    }

    String doToString() {
        String s = getFirstString();
        interp.getToken();
        if (interp.token==',') {
            double value = Tools.parseDouble(s);
            s = IJ.d2s(value, (int)interp.getExpression());
            interp.getToken();
        }
        if (interp.token!=')') interp.error("')' expected");
        return s;
    }
    
    double matches() {
        String str = getFirstString();
        String regex = getLastString();
        boolean matches = str.matches(regex);
        return matches?1.0:0.0;
    }
    
    void waitForUser() {
        if (waitForUserDialog!=null && waitForUserDialog.isVisible())
            interp.error("Duplicate call");
        String title = getFirstString();
        String text;
        if (interp.nextToken()==',')
            text = getLastString();
        else {
            text = title;
            title = "Action Required";
            interp.getRightParen();
        }
        waitForUserDialog = new WaitForUserDialog(title, text);
        waitForUserDialog.show();
    }
    
    double getStringWidth() {
        resetImage();
        ImageProcessor ip = getProcessor();
        setFont(ip);
        return ip.getStringWidth(getStringArg()); 
    }

    String doList() {
        interp.getToken();
        if (interp.token!='.')
            interp.error("'.' expected");
        interp.getToken();
        if (!(interp.token==WORD||interp.token==ARRAY_FUNCTION))
            interp.error("Function name expected: ");
        if (props==null)
            props = new Properties();
        String value = null;
        String name = interp.tokenString;
        if (name.equals("get")) {
            value = props.getProperty(getStringArg());
            value = value!=null?value:"";
        } else if (name.equals("set")||name.equals("add")||name.equals("put"))
            props.setProperty(getFirstString(), getLastString());
        else if (name.equals("clear")||name.equals("reset"))
            props.clear();
        else if (name.equals("setList"))
            setProperties();
        else if (name.equals("getList"))
            value = getProperties();
        else if (name.equals("size")||name.equals("getSize")) {
            interp.getParens();
            value = ""+props.size();
        } else
            interp.error("Unrecognized List function");
        return value;
    }
    
    void setProperties() {
        String list = getStringArg();
        props.clear();
        try {
            InputStream is = new ByteArrayInputStream(list.getBytes("utf-8"));
            props.load(is);
        } catch(Exception e) {
            interp.error(""+e);
        }
    }
    
    String getProperties() {
        interp.getParens();
        String list = props.toString();
        list = list.substring(1, list.length()-1);
        list = list.replaceAll(", ", "\n");
        return list;
    }

    void makePoint() {
        int x = (int)getFirstArg();
        int y = (int)getLastArg();
        IJ.makePoint(x, y);
        resetImage(); 
    }

} // class Functions