/// Evolver Copyright 2000, Morgan McGuire
/// matrix@alum.mit.edu   http://graphics3d.com/matrix
import java.io.*;
import java.lang.reflect.*;
import java.util.Vector;
import java.util.HashMap;

/**
 * Contains parameters affecting mating and simulation.  These can be
 * edited using an associated GUI panel.  This class also contains
 * helper functions for generating new expressions and subexpressions.
 */
public class GeneticParameters extends ClassLoader
{
    // Mutations:
    public static final int CHANGE_OPERATOR_MUTATION = 0;
    public static final int ENCLOSE_IN_NEW_CALL_MUTATION = 1;
    public static final int NEW_EXPRESSION_MUTATION = 2;
    public static final int SIMPLIFY_MUTATION = 3;
    public static final int CLONE_SIBLING_MUTATION = 4;
    public static final int SCALAR_CONSTANT_MUTATION = 5;
    public static final int VECTOR_CONSTANT_MUTATION = 6;

    static final int NUM_MUTATIONS = VECTOR_CONSTANT_MUTATION + 1;
  
    /**
   * Descriptions and details about each kind of mutation.  The order
   * of the values in this array must match the mutation constants.  
   */
    static MutationDescription mutationDescription[];

    /**
   * Maximum change in a scalar due to mutation.
   */
    double scalarDisturbance = 0.2;

    /**
   * Maximum change in a vector's individual components due to
   * mutation.  
   */
    double vectorDisturbance = 0.2;

    /**
   * Probability of a mutatation before accounting for parental
   * complexity.  
   */
    double baseMutationProbability = 0.01;

    OperatorDescription operatorDescription[];

  /**
   * A hash table mapping operator names (as strings) to ELOperators
   * to allow fast parsing of expressions.
   */
    HashMap operatorsByName;
  
    /**
   * The probability that any mating will be crossover (vs a tree
   * walk).  The probability of a tree walk is 1 -
   * crossoverProbability.
   */
    double crossoverProbability = 0.5;

    double scalarFrequency = 0.3;
    double vectorFrequency = 0.3;
    double callFrequency = 0.6;

    double callProbability, scalarProbability, vectorProbability;

    /**
   * @param operatorPath a path that can be searched for .class files
   * implementing ELOperator.
   */
    public GeneticParameters(String operatorPath)
    {
	createMutationDescriptions();
	loadOperators(operatorPath);
	loadSettings("default.gp");
	computeProbabilities();
    }


    /**
   * Computes the normalized mutation and operator probabilities.
   * Call this whenever frequencies change.
   */
    void computeProbabilities()
    {
	double sum = 0.0;

	for(int m = 0; m < mutationDescription.length; m++) {
	    sum += mutationDescription[m].frequency;
	}

	for(int m = 0; m < mutationDescription.length; m++) {
	    mutationDescription[m].probability = mutationDescription[m].frequency / sum;
	}

	sum = 0.0;

	for(int o = 0; o < operatorDescription.length; o++) {
	    if (operatorDescription[o].enabled) {
		sum += operatorDescription[o].frequency;
	    }
	}

	for(int o = 0; o < operatorDescription.length; o++) {
	    if (operatorDescription[o].enabled) {
		operatorDescription[o].probability = operatorDescription[o].frequency / sum;
	    }
	}

	sum = scalarFrequency + vectorFrequency + callFrequency;
    
	callProbability = callFrequency / sum;
	vectorProbability = vectorFrequency / sum;
	scalarProbability = scalarFrequency / sum;
    }

    /**
   * Sets all probabilities from a file.  The file has multiple clauses of the form:
   *
   * operator <operator> { <name> = <value> }
   *
   * where name = "frequency" or "enabled" and value is a number
   * between 0 and 1 or true or false.  
   */
  // The helper read functions all assume that they are to read the
  // current token, then advance the tokenizer.
    private void loadSettings(String file)
    {
	System.out.print("Loading settings (" + file + ")...");
	StreamTokenizer tokenizer = null;
    
	try {
	    tokenizer = new StreamTokenizer(new BufferedReader(new FileReader(file)));

	    // configure the parser
	    tokenizer.parseNumbers();
	    tokenizer.slashSlashComments(true);
	    tokenizer.slashStarComments(true);
	    tokenizer.lowerCaseMode(false);
	    tokenizer.eolIsSignificant(false);

	    tokenizer.nextToken();      
	    while (tokenizer.ttype != StreamTokenizer.TT_EOF) {
        
		String identifier = readIdentifier(tokenizer);

		if (identifier.equals("operator")) {

		    String operatorName = readIdentifier(tokenizer);

		    readSymbol(tokenizer, '{');
          
		    double frequency = 0.5;
		    boolean enabled = false;
		    while (tokenizer.ttype != '}') {
			identifier = readIdentifier(tokenizer);
			readSymbol(tokenizer, '=');
            
			if (identifier.equals("frequency")) {
			    frequency = readProbability(tokenizer);
			} else if (identifier.equals("enabled")) {
			    enabled = readBoolean(tokenizer);
			} else {
			    throw new ParseException("Unexpected identifier: '" + identifier + "'");
			}
		    }
          
		    // consume the closing brace
		    tokenizer.nextToken();

		    // Write to operator description.  Ignore operators we
		    // couldn't find.  There are a lot of operators, so run this
		    // loop backwards for a bit more speed.

		    for(int od = operatorDescription.length - 1; od >= 0; od--) {
			if (operatorDescription[od].name.equals(operatorName)) {
			    operatorDescription[od].frequency = frequency;
			    operatorDescription[od].enabled = enabled;
			    break;
			}
		    } // for

		} else if (identifier.equals("scalarDisturbance")) {
		    readSymbol(tokenizer, '=');
		    scalarDisturbance = readNumber(tokenizer);
		} else if (identifier.equals("vectorDisturbance")) {
		    readSymbol(tokenizer, '=');
		    vectorDisturbance = readNumber(tokenizer);
		} else if (identifier.equals("mutationProbability")) {
		    readSymbol(tokenizer, '=');
		    baseMutationProbability = readProbability(tokenizer);
		} else if (identifier.equals("crossoverProbability")) {
		    readSymbol(tokenizer, '=');
		    crossoverProbability = readProbability(tokenizer);

		} else if (identifier.equals("scalarFrequency")) {
		    readSymbol(tokenizer, '=');
		    scalarFrequency = readProbability(tokenizer);
		} else if (identifier.equals("vectorFrequency")) {
		    readSymbol(tokenizer, '=');
		    vectorFrequency = readProbability(tokenizer);
		} else if (identifier.equals("callFrequency")) {
		    readSymbol(tokenizer, '=');
		    callFrequency = readProbability(tokenizer);
		} else if (identifier.endsWith("Frequency")) {

		    boolean found = false;
		    readSymbol(tokenizer, '=');

		    // try to find a corresponding mutation probability
		    for(int m = 0; m < mutationDescription.length; m++) {
			if (identifier.equals(mutationDescription[m].name + "Frequency")) {
			    mutationDescription[m].frequency = readProbability(tokenizer);
			    found = true;
			    break;
			}
		    }
          
		    if (! found) {
			throw new ParseException("Unknown frequency '" + identifier + "'");
		    }

		} else {
		    throw new ParseException("Unknown identifier '" + identifier + "'");
		}

	    } // while
     
	} catch (FileNotFoundException e) {
	    System.err.println();
	    System.err.println();
	    System.err.println("Warning: " + file + " not found.");
	    System.err.println();
	} catch (IOException e) {
	    System.err.println();
	    System.err.println();
	    System.out.println(e);
	    System.err.println();
	} catch (ParseException e) {
	    System.err.println();
	    System.err.println();
	    System.err.println("*** Error on line " + tokenizer.lineno() + " of " + file + ": " + e + " ***");
	    System.err.println();
	}
	System.out.println("done");
    }
  
    private double readProbability(StreamTokenizer tokenizer) throws ParseException, IOException
    {
	return Math.min(Math.max(0.0, readNumber(tokenizer)), 1.0);
    }

    private double readNumber(StreamTokenizer tokenizer) throws ParseException, IOException
    { 
	if (tokenizer.ttype != StreamTokenizer.TT_NUMBER) {
	    throw new ParseException("Number expected.");
	} else {
	    double n = tokenizer.nval;
	    tokenizer.nextToken();
	    return n;
	}   
    }

    private boolean readBoolean(StreamTokenizer tokenizer) throws ParseException, IOException
    { 
	if (tokenizer.ttype != StreamTokenizer.TT_WORD) {
	    throw new ParseException("Boolean expected.");
	} else {
	    boolean b = true;

	    if (tokenizer.sval.equals("true")) {
		b = true;
	    } else if (tokenizer.sval.equals("false")) {
		b = false;
	    } else {
		throw new ParseException("Boolean expected, '" + tokenizer.sval + "' encountered.");
	    }

	    tokenizer.nextToken();
	    return b;
	}   
    }

    private void readKeyWord(StreamTokenizer tokenizer, String expectedValue) throws ParseException, IOException
    {
	String value = readIdentifier(tokenizer);
    
	if (! value.equals(expectedValue)) {
	    throw new ParseException("Expected '" + expectedValue + "' and found '" +
				     value + "'");

	}
    }

    private String readIdentifier(StreamTokenizer tokenizer) throws ParseException, IOException
    {
	if (tokenizer.ttype != StreamTokenizer.TT_WORD) {
	    throw new ParseException("Identifier expected.  Token type = " + tokenizer.ttype + " Token value = '" +
				     tokenizer.sval + "' or '" + tokenizer.nval + "' or '" + 
				     new Character((char)(tokenizer.ttype)) + "'");
	} else {
	    String s = tokenizer.sval;
	    tokenizer.nextToken();
	    return s;
	}   
    }

    private void readSymbol(StreamTokenizer tokenizer, char symbol) throws ParseException, IOException
    {
	if (tokenizer.ttype != symbol) {
	    throw new ParseException("'" + new Character(symbol) + 
				     "' symbol expected.  Token type = " + tokenizer.ttype + " Token value = '" +
				     tokenizer.sval + "' or '" + tokenizer.nval + "' or '" + 
				     new Character((char)(tokenizer.ttype)) + "'");
	} else {
	    tokenizer.nextToken();
	}   
    }


    private void loadOperators(String operatorPath)
    {
	//System.out.print("Finding operators... [ ");

	// Generate a list of all class files
	String classFile[] = 
	    new File(operatorPath).list(new FilenameFilter() {
		public boolean accept(File dir, String name) {
		    return name.endsWith(".class");
		}});
    
	// This will be our result array
	Class operators[] = new Class[classFile.length];
	int numOperators = 0;

	// Generate each class name
	for (int c = 0; c < classFile.length; c++) {
	    String name = classFile[c].substring(0, classFile[c].length() - 6);

	    // Ignore names with a $ in them; they are inner classes.
	    if (name.indexOf('$') == -1) {
		try {
		    // Load the class and see if it implements ELOperator
		    Class cls = findClass(name);
		    int modifiers = cls.getModifiers();
          
		    // don't load abstract or interface classes
		    if (! Modifier.isInterface(modifiers) && ! Modifier.isAbstract(modifiers)) {
            
			Class interfaces[] = cls.getInterfaces();
                          
			// Check every interface
			for(int i = 0; i < interfaces.length; i++) {
              
			    // Try for a string match on the name
			    if (interfaces[i].getName().equals("ELOperator")) {
				// It is a match!  Record this and exit the interface loop abruptly.
				operators[numOperators] = cls;
				numOperators++;
				//System.out.print(cls.getName() + " ");
				break;
			    }
			}
		    }
		} catch (ClassNotFoundException e) {
		    System.err.println(e);
		}
	    }
	}

	//System.out.println("] done");

	System.out.print("Loading operators... [ ");

	createOperatorDescriptions(operators, numOperators);

	System.out.println("] done");

    }

    /**
   * Creates mutation descriptions
   */
    private void createMutationDescriptions()
    {
	mutationDescription = new MutationDescription[NUM_MUTATIONS];

	mutationDescription[CHANGE_OPERATOR_MUTATION] = 
	    new MutationDescription("changeOperator", "Change Operator", 0.5);

	mutationDescription[ENCLOSE_IN_NEW_CALL_MUTATION] =
	    new MutationDescription("encloseInNewCall", "Enclose in new Call", 0.5);

	mutationDescription[NEW_EXPRESSION_MUTATION] =
	    new MutationDescription("newExpression", "New Expression", 0.5);

	mutationDescription[SIMPLIFY_MUTATION] = 
	    new MutationDescription("simplify", "Simplify", 0.5);

	mutationDescription[CLONE_SIBLING_MUTATION] = 
	    new MutationDescription("cloneSibling", "Clone Sibling", 0.5);

	mutationDescription[SCALAR_CONSTANT_MUTATION] = 
	    new MutationDescription("scalarValueMutation", "Scalar Mutation", 0.5);

	mutationDescription[VECTOR_CONSTANT_MUTATION] = 
	    new MutationDescription("vectorValueMutation", "Vector Mutation", 0.5);
    }

    /**
   * Creates descriptions for the operators, building the operatorDescription and
   * operatorsByName fields.
   */
    private void createOperatorDescriptions(Class operator[], int numOperators) 
    {
	operatorDescription = new OperatorDescription[numOperators];
	operatorsByName = new HashMap();

	for (int o = 0; o < numOperators; o++) {
	    System.out.print(operator[o].getName() + " ");
	    operatorDescription[o] = new OperatorDescription();
	    operatorDescription[o].operator = newOperatorInstance(operator[o]);
	    operatorDescription[o].frequency = 0.0;
	    operatorDescription[o].enabled = false;
	    operatorDescription[o].name = operator[o].getName();
	    operatorsByName.put(operatorDescription[o].name, operatorDescription[o].operator);
	}
    }

    /**
   * Instantiates an operator subclass.
   */
    private ELOperator newOperatorInstance(Class operatorClass)
    {
	ELOperator operator = null;
    
	try {
	    operator = (ELOperator) operatorClass.newInstance();
	} catch (ExceptionInInitializerError e) {

	    // An exception occurred inside the constructor
	    System.err.println();
	    System.err.println(e.getException());
	    throw new Error("Cannot instantiate " + operatorClass);

	} catch (Exception e) {
	    System.err.println("" + e);
	    throw new Error("Cannot instantiate " + operatorClass);
	}
    
	return operator;
    }

    /**
   * Generates a random operator based the normalized operator
   * probabilities.
   */
    public ELOperator randomOperator()
    {
	// Generates a random number on the range (0,1]
	double r = 1.0 - Math.random();
	double accum = 0.0;

	for(int o = 0; o < operatorDescription.length; o++) {
	    if (operatorDescription[o].enabled) {
		accum += operatorDescription[o].probability;
		if (accum >= r) {
		    return operatorDescription[o].operator;
		}
	    }
	}

	throw new Error("Internal error while choosing a random operator.");
    }

    /**
   * Generates a new random expression of any type.
   */
    public ELExpression randomExpression()
    {
	double r = 1.0 - Math.random();
	double accum = 0.0;
    
	accum += callProbability;
	if (accum >= r) {
	    return randomCall();
	} 
    
	accum += vectorProbability;
	if (accum >= r) {
	    return new ELVector(Math.random() * 2 - 1.0, Math.random() * 2 - 1.0, Math.random() * 2 - 1.0);
	} 

	accum += scalarProbability;
	if (accum >= r) {
	    return new ELScalar(Math.random() * 2 - 1.0);
	} 

	throw new Error("accum = " + accum + " r = " + r);
    }
    
    public ELCall randomCall()
    {
	ELOperator operator = randomOperator();

	int min = operator.getMinNumArguments();
    
	ELExpression subExpression[] = new ELExpression[min];
	int i = 0;
	while (i < min) {
	    subExpression[i] = randomExpression();
	    i++;
	}
    
	return new ELCall(operator, subExpression);
    }

    /**
   * Generates a random call of at least one argument, using
   * expression as the first argument.
   */
    public ELCall randomCallWithOperator(ELExpression expression)
    {
	ELOperator operator = randomOperator();

	while (operator.getMinNumArguments() < 1) {
	    operator = randomOperator();
	}

	int min = operator.getMinNumArguments();
    
	ELExpression subExpression[] = new ELExpression[min];
	int i = 0;
	subExpression[i] = expression;

	i++;
	while (i < min) {
	    subExpression[i] = randomExpression();
	    i++;
	}
    
	return new ELCall(operator, subExpression);
    }

    /**
   * Genetically mates two expressions and produces a single
   * offspring.  The type of reproduction that occurs is determined
   * probabilistically by the crossoverProbability.
   *
   * For a crossover mating, one parent is cloned, then a randomly
   * chosen subexpression from the other parent is cloned and
   * substituted at a random location.
   */
    public ELExpression mate(ELExpression a, ELExpression b) 
    {
	if (Math.random() < crossoverProbability) {
	    // Crossover
	    return crossoverMate(a, b);
	} else {
	    // Tree walk
	    return walkMate(a, b);
	}
    }

    /**
   * Always returns a clone.
   */
    private ELExpression crossoverMate(ELExpression a, ELExpression b) 
    {

	// Choose a random parent
	ELExpression receivingParent;
	ELExpression donorParent;
    
	if (Math.random() < 0.5) {
	    receivingParent = a;
	    donorParent = b;
	} else {
	    receivingParent = b;
	    donorParent = a;
	}
    
	ELExpression child = receivingParent.geneticClone(null, this);
	ELExpression crossExpression = donorParent.randomNestedSubexpression();
    
	// Insert into child
	int position = (int)Math.floor(Math.random() * receivingParent.numNestedSubexpressions());

	return child.cloneWithReplacement(crossExpression, position);
    }

    /**
   * Always returns a clone
   */
    private ELExpression walkMate(ELExpression a, ELExpression b)
    {
	if ((a instanceof ELCall) && (b instanceof ELCall) && 
	    (((ELCall) a).operator == ((ELCall) b).operator)) {
      
	    ELCall ac = (ELCall) a;
	    ELCall bc = (ELCall) b;

	    // Choose a number of arguments between the length of a.args and
	    // b.args.  When they are the same length, this will always
	    // compute that length.
	    double r = Math.random();
	    int numArgs = (int)Math.round((ac.child.length * r) + (bc.child.length * (1.0 - r)));
	    ELExpression args[] = new ELExpression[numArgs];

	    for (int i = 0; i < numArgs; i++) {
		if (i >= ac.child.length) {
		    args[i] = bc.child[i];
		} else if (i >= bc.child.length) {
		    args[i] = ac.child[i];
		} else {
		    // Recursively mate the subexpressions.  If they are the
		    // same, this will return the identical value.  The arguments 
		    // will get cloned by walkmate.
		    args[i] = walkMate(ac.child[i], bc.child[i]);
		}
	    }

	    // Clone the result to allow mutations to occur at any point.
	    return new ELCall(ac.operator, args).geneticClone(null, this);

	} else {
	    if (Math.random() < 0.5) {
		return a.geneticClone(null, this);
	    } else
		return b.geneticClone(null, this);
	}
    }

    /**
   * Creates a new expression containing ELMorphInterpolators that
   * will interpolate between two expressions.  The two expressions
   * are completely cloned (excepting operators).
   */
    public ELExpression createGeneticMorphExpression(ELExpression a, ELExpression b)
    {
	if ((a instanceof ELCall) && (b instanceof ELCall) && 
	    (((ELCall) a).operator == ((ELCall) b).operator) &&
	    (((ELCall) a).numSubexpressions() == ((ELCall) b).numSubexpressions())) {
      
	    ELCall aCall = (ELCall) a;
	    ELCall bCall = (ELCall) b;

	    // Create new argument expressions
	    ELExpression subexpression[] = new ELExpression[aCall.numSubexpressions()];
      
	    for(int e = subexpression.length - 1; e >= 0; e--) {
		subexpression[e] = createGeneticMorphExpression(aCall.child[e], bCall.child[e]);
	    }

	    // Return the new call.
	    return new ELCall(aCall.operator, subexpression);

	} else {
	    // These are not the same call expression...
	    // If these are the same expression, return that expression, 
	    // otherwise return an interpolator.  Note that either way, '
	    // the recursion stops here.
	    if (a.sameExpression(b)) {
		return (ELExpression) a.clone();
	    } else {
		return new ELMorphInterpolator((ELExpression)a.clone(), (ELExpression)b.clone());
	    }
	}
    }

    /**
   * Randomly chooses whether a mutation will occur based on the
   * length of the parent expression and the base mutation probability
   * and returns true if a mutation will occur. This is used by
   * ELExpression implementations in the geneticClone() method.
   *
   * @param parent The <B>parent</B> of the expression that may be
   * mutated.  The probability of mutation is not directly depenent on
   * the expression itself.  */
    public boolean mutationOccurs(ELCall parent)
    {
	double mutationProbability;

	if (parent == null) {
	    mutationProbability = baseMutationProbability;
	} else {
	    mutationProbability = baseMutationProbability / parent.numNestedSubexpressions();
	}

	return (Math.random() <= mutationProbability);
    }

    /**
   * Chooses a random type of expression mutation based on the
   * probabilities provided.  The return value is one of the _MUTATION
   * constants this class defines.  
   */
    public int randomMutationType()
    {
	// random threshold cumulative probability must exceed
	double n = Math.random();

    // index of mutation
	int m = 0;

	// cumulative probability
	double p = mutationDescription[m].probability;;

	int length = mutationDescription.length;

    // Find the mutation assigned to this probability block.
	while (n > p) {
      
	    if ((m >= length) || (p > 1.0)) {
		throw new Error("Internal error while choosing random mutation type (m = " + 
				m + ", p = " + p + ", n = " + n + ")");
	    }

	    p += mutationDescription[m].probability;
	    m++;
	}

	return m;
    }
  
    // inherited from ClassLoader
    protected Class findClass(String name) throws ClassNotFoundException
    {
	Class c = findSystemClass(name);

	if (c != null) {
	    return c;
	} else {
	    throw new ClassNotFoundException();
	}
    }

    public String unparseELExpression(ELExpression e) {
	return e.unparse();
    }

    /**
     * Parses a string of EL code.  This is used for unserializing
     * ELExpressions.  Will return the scalar 0 if anything goes wrong
     * while parsing.
     */
    // DESIGN NOTE: This method is here because it needs access to the
    // operator list.
    public ELExpression parseELExpression(String exprString)
    {
	return parseELExpression(exprString, true);
    }

    /**
     * If supressErrors is true, parse errors are printed to System.err rather
     * than thrown as exceptions.
     */
    public ELExpression parseELExpression(String exprString, boolean suppressErrors)
    {
	StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(exprString));
	// configure the parser
	tokenizer.parseNumbers();
	tokenizer.slashSlashComments(true);
	tokenizer.slashStarComments(true);
	tokenizer.lowerCaseMode(false);
	tokenizer.eolIsSignificant(false);

	try {
	    tokenizer.nextToken();
	    return readExpression(tokenizer);
	} catch (IOException e) {
	    System.err.println("While parsing '" + exprString + "':");
	    System.err.println(e);
	    return null;//new ELScalar(0);
	} catch (ParseException e) {
	    if (suppressErrors) {
		System.err.println("While parsing '" + exprString + "':");
		System.err.println(e);
		return null;//new ELScalar(0);
	    } else {
		throw new ParseError(e.toString());
	    }
	}
    }

    /**
   * Reads an ELExpression from the tokenizer and returns it, having consumed all tokens in the expression.
   */
    private ELExpression readExpression(StreamTokenizer tokenizer) throws ParseException, IOException
    {
	// Look at the next token.
	if (tokenizer.ttype == '{') {
	    // Vector constant
	    // Consume the open '{'
	    tokenizer.nextToken();
	    double r = readNumber(tokenizer);
	    readSymbol(tokenizer, ',');
	    double g = readNumber(tokenizer);
	    readSymbol(tokenizer, ',');
	    double b = readNumber(tokenizer);
	    if (tokenizer.ttype != '}') {
		throw new ParseException("'}' expected at end of vector constant.");
	    }
	    tokenizer.nextToken();

	    return new ELVector(r, g, b);
	} else if (tokenizer.ttype == '[') {
	    // Parameter
	    // Consume the open '['
	    tokenizer.nextToken();
	    // Read the operator name
	    String parameterName = tokenizer.sval;
	    tokenizer.nextToken();

	    if (tokenizer.ttype != ']') {
		throw new ParseException("']' expected at end of parameter.");
	    }
	    // Consume the close ']'
	    tokenizer.nextToken();

	    return new ELParameter(parameterName);

	} else if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
	    // Scalar number
	    return new ELScalar(readNumber(tokenizer));

	} else if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
	    // Call
      
	    // Read the operator name
	    String operatorName = tokenizer.sval;
	    tokenizer.nextToken();

	    // Look up the operator
	    ELOperator operator = (ELOperator)operatorsByName.get(operatorName);
      
	    if (operator == null) {
		throw new ParseException("Unrecognized operator: " + operatorName);
	    }
      
	    Vector subExpressions = new Vector();

	    // Consume the open paren
	    readSymbol(tokenizer, '(');
	    while (tokenizer.ttype != ')') {
		subExpressions.addElement(readExpression(tokenizer));

		// verify that a comma is next
		if (tokenizer.ttype == ',') {
		    // consume the comma
		    readSymbol(tokenizer, ',');
		} else if (tokenizer.ttype != ')') {
		    throw new ParseException("Expected )");
		}
	    }

	    // Consume the close paren
	    readSymbol(tokenizer, ')');

	    // Copy the subexpressions into a strongly typed array.
	    ELExpression subExprELExpressions[] = new ELExpression[subExpressions.size()];

	    for(int e = 0; e < subExprELExpressions.length; e++) {
		subExprELExpressions[e] = (ELExpression)subExpressions.get(e);
	    }
      
	    // Construct the call
	    return new ELCall(operator, subExprELExpressions);
      
	} else if (tokenizer.ttype == StreamTokenizer.TT_EOF) {
	    throw new ParseException("Can't parse expression; ran out of tokens");
	} else {
	    throw new ParseException("Can't parse expression; unexpected " +
				     "token detected.  Token type = " + tokenizer.ttype + " Token value = '" +
				     tokenizer.sval + "' or '" + tokenizer.nval + "' or '" + 
				     new Character((char)(tokenizer.ttype)) + "'");
	}
    }
}


class MutationDescription
{
    String name;
    String longDescription;
    double frequency;
    double probability;

    public MutationDescription(String name,
			       String longDescription,
			       double frequency)
    {
	this.name = name;
	this.longDescription = longDescription;
	this.frequency = frequency;
    }

}

class ParseException extends Exception
{
    public ParseException(String s) 
    {
	super(s);
    }
}
