JVariableList::MatchResult
JVariableList::FindUniqueVarName
	(
	const JCharacter*	prefix,
	JIndex*				index,
	JString*			maxPrefix
	)
	const
{
	assert( !JStringEmpty(prefix) );

	const JSize count = GetElementCount();
	JArray<JIndex> matchList;

	for (JIndex i=1; i<=count; i++)
		{
		const JString& name = GetVariableName(i);
		if (name == prefix)
			{
			*index     = i;
			*maxPrefix = name;
			return kSingleMatch;
			}
		else if (JStringBeginsWith(name, name.GetLength(), prefix))
			{
			matchList.AppendElement(i);
			}
		}

	const JSize matchCount = matchList.GetElementCount();
	if (matchCount == 0)
		{
		*index = 0;
		maxPrefix->Clear();
		return kNoMatch;
		}
	else if (matchCount == 1)
		{
		*index     = matchList.GetElement(1);
		*maxPrefix = GetVariableName(*index);
		return kSingleMatch;
		}
	else
		{
		*maxPrefix = GetVariableName( matchList.GetElement(1) );
		for (JIndex i=2; i<=matchCount; i++)
			{
			const JString& varName   = GetVariableName( matchList.GetElement(i) );
			const JSize matchLength  = JCalcMatchLength(*maxPrefix, varName);
			const JSize prefixLength = maxPrefix->GetLength();
			if (matchLength < prefixLength)
				{
				maxPrefix->RemoveSubstring(matchLength+1, prefixLength);
				}
			}
		*index = 0;
		return kMultipleMatch;
		}
}
JParseResult
JParseAsNegation
	(
	const JCharacter*		origExpr,
	const JSize				origLength,
	const JVariableList*	theVariableList,
	JDecision**				theDecision
	)
{
	*theDecision = NULL;

	// remove enclosing parentheses

	const JCharacter* expr = origExpr;
	const JSize length     = JStripParentheses(&expr, origLength);

	// must start with "not "

	if (!JStringBeginsWith(expr, length, JPGetBooleanNOTString()))
		{
		return kJNotMyProblem;
		}

	// parse the rest of the string as a decision

	const JSize offset = JPGetBooleanNOTStringLength();

	JDecision* arg = NULL;
	if (!JRecurseDecision(expr + offset, length - offset, theVariableList, &arg))
		{
		return kJParseError;
		}

	// return JBooleanNOT object

	*theDecision = new JBooleanNOT(arg);
	assert( *theDecision != NULL );
	return kJParsedOK;
}
JParseResult
JParseAsFunctionWithArgs
	(
	const JCharacter*		origExpr,
	const JSize				origLength,
	const JVariableList*	theVariableList,
	JFunction**				theFunction,
	const JBoolean			allowUIF
	)
{
	*theFunction = NULL;

	// remove enclosing parentheses

	const JCharacter* expr = origExpr;
	const JSize length     = JStripParentheses(&expr, origLength);

	// check if expr starts with a function name

	const JSize fnCount            = JPGetStdFunctionCount();
	const JStdFunctionInfo* fnInfo = JPGetStdFunctionInfo();

	JBoolean found = kJFalse;
	JFunctionType type = kJSquareRootType;
	const JCharacter* argsStart = NULL;
	JSize argsLength = 0, argCount = 0, nameLength = 0;
	for (JIndex i=1; i<=fnCount; i++)
		{
		if (JStringBeginsWith(expr, length, fnInfo[i-1].name))
			{
			found    = kJTrue;
			type     = fnInfo[i-1].type;
			argCount = fnInfo[i-1].argCount;

			nameLength = strlen(fnInfo[i-1].name);
			argsStart  = expr + nameLength;
			argsLength = length - nameLength - 1;

			break;
			}
		}
	if (!found)
		{
		return kJNotMyProblem;
		}

	// check if the argument block is correctly closed

	if (!((*(expr + nameLength-1) == '(' && *(expr + length-1) == ')') ||
		  (*(expr + nameLength-1) == '[' && *(expr + length-1) == ']')) )
		{
		return kJNotMyProblem;
		}

	// create the appropriate object

	JFunctionWithArgs* stdFunction = NULL;
	if (type == kJSquareRootType)
		{
		stdFunction = new JSquareRoot();
		}
	else if (type == kJAbsValueType)
		{
		stdFunction = new JAbsValue();
		}
	else if (type == kJSignType)
		{
		stdFunction = new JAlgSign();
		}
	else if (type == kJRoundType)
		{
		stdFunction = new JRoundToInt();
		}
	else if (type == kJTruncateType)
		{
		stdFunction = new JTruncateToInt();
		}

	else if (type == kJLog10Type)
		{
		stdFunction = new JLogB();	// will set base later
		}
	else if (type == kJLogEType)
		{
		stdFunction = new JLogE();
		}
	else if (type == kJLog2Type)
		{
		stdFunction = new JLogB();	// will set base later
		}

	else if (type == kJSineType)
		{
		stdFunction = new JSine();
		}
	else if (type == kJCosineType)
		{
		stdFunction = new JCosine();
		}
	else if (type == kJTangentType)
		{
		stdFunction = new JTangent();
		}
	else if (type == kJArcSineType)
		{
		stdFunction = new JArcSine();
		}
	else if (type == kJArcCosineType)
		{
		stdFunction = new JArcCosine();
		}
	else if (type == kJArcTangentType)
		{
		stdFunction = new JArcTangent();
		}

	else if (type == kJHypSineType)
		{
		stdFunction = new JHypSine();
		}
	else if (type == kJHypCosineType)
		{
		stdFunction = new JHypCosine();
		}
	else if (type == kJHypTangentType)
		{
		stdFunction = new JHypTangent();
		}
	else if (type == kJArcHypSineType)
		{
		stdFunction = new JArcHypSine();
		}
	else if (type == kJArcHypCosineType)
		{
		stdFunction = new JArcHypCosine();
		}
	else if (type == kJArcHypTangentType)
		{
		stdFunction = new JArcHypTangent();
		}

	else if (type == kJRealPartType)
		{
		stdFunction = new JRealPart();
		}
	else if (type == kJImagPartType)
		{
		stdFunction = new JImagPart();
		}
	else if (type == kJPhaseAngleType)
		{
		stdFunction = new JPhaseAngle();
		}
	else if (type == kJConjugateType)
		{
		stdFunction = new JConjugate();
		}

	else if (type == kJLogBType)
		{
		stdFunction = new JLogB();
		}
	else if (type == kJArcTangent2Type)
		{
		stdFunction = new JArcTangent2();
		}
	else if (type == kJRotateType)
		{
		stdFunction = new JRotateComplex();
		}

	else if (type == kJMaxFuncType)
		{
		stdFunction = new JMaxFunc();
		}
	else if (type == kJMinFuncType)
		{
		stdFunction = new JMinFunc();
		}
	else if (type == kJParallelType)
		{
		stdFunction = new JParallel();
		}
	else
		{
		cerr << "JParseAsFunctionWithArgs:  unknown function type" << endl;
		return kJParseError;
		}
	assert( stdFunction != NULL );

	// parse each argument

	const JCharacter* argSeparatorStr = JPGetArgSeparatorString();
	const JSize argSeparatorLength    = JPGetArgSeparatorStringLength();

	if (argCount == 1)
		{
		// parse the entire string as the single argument

		JFunction* arg = NULL;
		const JBoolean ok =
			JRecurseFunction(argsStart, argsLength, theVariableList, &arg, allowUIF);
		if (ok && (type == kJLog10Type || type == kJLog2Type))
			{
			JConstantValue* base = NULL;
			if (type == kJLog10Type)
				{
				base = new JConstantValue(10.0);
				}
			else if (type == kJLog2Type)
				{
				base = new JConstantValue(2.0);
				}
			assert( base != NULL );
			stdFunction->SetArg(1, base);
			stdFunction->SetArg(2, arg);
			*theFunction = stdFunction;
			return kJParsedOK;
			}
		else if (ok)
			{
			stdFunction->SetArg(1, arg);
			*theFunction = stdFunction;
			return kJParsedOK;
			}
		else
			{
			delete stdFunction;
			return kJParseError;
			}
		}
	else if (argCount != kJUnlimitedArgs)
		{
		// parse all but the last argument by searching forward
		// for commas with JFindFirstOperator

		const JCharacter* argStart = argsStart;
		JSize remainderLength = argsLength;
		for (JIndex i=1; i<argCount; i++)
			{
			JSize offset;
			if (!JFindFirstOperator(argStart, remainderLength, argSeparatorStr, &offset))
				{
				JString errorStr(expr, length);
				errorStr.Prepend("\"");
				errorStr += "\" has too few arguments";
				(JGetUserNotification())->ReportError(errorStr);
				delete stdFunction;
				return kJParseError;
				}
			JFunction* arg = NULL;
			if (!JRecurseFunction(argStart, offset, theVariableList, &arg, allowUIF))
				{
				delete stdFunction;
				return kJParseError;
				}
			stdFunction->SetArg(i, arg);
			argStart += offset + argSeparatorLength;
			remainderLength -= offset + argSeparatorLength;
			}

		// make sure that there is only one argument left

		JSize offset;
		if (JFindFirstOperator(argStart, remainderLength, argSeparatorStr, &offset))
			{
			JString errorStr(expr, length);
			errorStr.Prepend("\"");
			errorStr += "\" has too many arguments";
			(JGetUserNotification())->ReportError(errorStr);
			delete stdFunction;
			return kJParseError;
			}

		// parse the last argument in the list

		JFunction* arg = NULL;
		if (!JRecurseFunction(argStart, remainderLength, theVariableList, &arg, allowUIF))
			{
			delete stdFunction;
			return kJParseError;
			}
		stdFunction->SetArg(argCount, arg);
		*theFunction = stdFunction;
		return kJParsedOK;
		}
	else	// argCount == kJUnlimitedArgs
		{
		// parse all the arguments by searching forward
		// for commas with JFindFirstOperator

		JIndex i=1;
		const JCharacter* argStart = argsStart;
		JSize remainderLength = argsLength;
		JBoolean foundArgSeparator = kJTrue;
		while (foundArgSeparator)
			{
			JSize offset;
			foundArgSeparator =
				JFindFirstOperator(argStart, remainderLength, argSeparatorStr, &offset);
			if (!foundArgSeparator)
				{
				offset = remainderLength;
				}
			JFunction* arg = NULL;
			if (!JRecurseFunction(argStart, offset, theVariableList, &arg, allowUIF))
				{
				delete stdFunction;
				return kJParseError;
				}
			stdFunction->SetArg(i, arg);
			i++;
			argStart += offset + argSeparatorLength;
			remainderLength -= offset + argSeparatorLength;
			}

		*theFunction = stdFunction;
		return kJParsedOK;
		}
}
JParseResult
JParseAsFunctionOfDiscrete
	(
	const JCharacter*		origExpr,
	const JSize				origLength,
	const JVariableList*	theVariableList,
	JFunction**				theFunction,
	const JBoolean			allowUIF
	)
{
	*theFunction = NULL;

	// remove enclosing parentheses

	const JCharacter* expr = origExpr;
	const JSize length     = JStripParentheses(&expr, origLength);

	// check if expr starts with a function name

	const JSize fnCount                   = JPGetFnOfDiscreteCount();
	const JFunctionOfDiscreteInfo* fnInfo = JPGetFnOfDiscreteInfo();

	JBoolean found = kJFalse;
	JFunctionType type = kJSquareRootType;
	const JCharacter* argStart = NULL;
	JSize argLength = 0, nameLength = 0;
	for (JSize i=1; i<=fnCount; i++)
		{
		if (JStringBeginsWith(expr, length, fnInfo[i-1].name))
			{
			found = kJTrue;
			type  = fnInfo[i-1].type;

			nameLength = strlen(fnInfo[i-1].name);
			argStart  = expr + nameLength;
			argLength = length - nameLength - 1;
			}
		}
	if (!found)
		{
		return kJNotMyProblem;
		}

	// check if the argument block is correctly closed

	if (!((*(expr + nameLength-1) == '(' && *(expr + length-1) == ')') ||
		  (*(expr + nameLength-1) == '[' && *(expr + length-1) == ']')) )
		{
		return kJNotMyProblem;
		}

	// parse the argument as a discrete variable name

	JIndex varIndex;
	JFunction* arrayIndex = NULL;

	const JParseResult result = JParseVariable(argStart, argLength, theVariableList,
											   &varIndex, &arrayIndex, allowUIF);
	if (result != kJParsedOK || !theVariableList->IsDiscrete(varIndex))
		{
		delete arrayIndex;

		JString errorStr(argStart, argLength);
		errorStr.Prepend("\"");
		errorStr += "\" is not a discrete variable";
		(JGetUserNotification())->ReportError(errorStr);
		return kJParseError;
		}

	// create the appropriate object

	if (type == kJDiscVarValueIndexType)
		{
		*theFunction = new JDiscVarValueIndex(theVariableList, varIndex, arrayIndex);
		}
	else if (type == kJDiscVarValueType)
		{
		*theFunction = new JDiscVarValue(theVariableList, varIndex, arrayIndex);
		}
	else
		{
		cerr << "JParseAsFunctionOfDiscrete:  unknown function type" << endl;
		delete arrayIndex;
		return kJParseError;
		}
	assert( *theFunction != NULL );

	return kJParsedOK;
}