Ejemplo n.º 1
0
        /// <summary>
        /// Takes the calcDef and extracts the first expression using the operator of the creator. This is called repeated until there are no more expressions of the operator.
        /// </summary>
        /// <param name="calcToolDef"></param>
        /// <param name="calcDef_upperCase"></param>
        /// <returns></returns>
        public MathOperationDefinition CreateMathOperation(CalculationToolDefinition calcToolDef, ref string calcDef, ref string calcDef_upperCase)
        {
            List <DataValueDefinition> valueObjects = new List <DataValueDefinition>(); // NOTE: this List will be owned by the MathOperationDefinition that we create
            List <string> operatorsUsed             = new List <string>();              // NOTE: this List will be owned by the MathOperationDefinition that we create

            int index;
            int indexOfFirstOperator    = -1;
            int indexAfterFirstOperator = -1;

            // find first matching operator
            for (index = 0; index < calcDef_upperCase.Length && indexOfFirstOperator < 0; index++)
            {
                for (int opNdx = 0; opNdx < numOperators; opNdx++)
                {
                    if (calcDef_upperCase[index] == supportedOperators[opNdx][0] ||
                        (supportedOperators[opNdx][0] == ' ' && Char.IsWhiteSpace(calcDef_upperCase[index])))
                    {
                        bool didNotFindConflict = true;
                        for (int y = 1; y < supportedOperators[opNdx].Length; y++)
                        {
                            if (index + y >= calcDef_upperCase.Length || // if we run out of calcDef, we have a conflict (non-match)
                                calcDef_upperCase[index + y] != supportedOperators[opNdx][y])
                            {
                                if (index + y < calcDef_upperCase.Length &&
                                    supportedOperators[opNdx][y] == ' ' && Char.IsWhiteSpace(calcDef_upperCase[index + y]))
                                {
                                    // special case where they don't have to match exactly
                                }
                                else
                                {
                                    didNotFindConflict = false;
                                    break;
                                }
                            }
                        }
                        if (didNotFindConflict)
                        {
                            indexOfFirstOperator    = index;
                            indexAfterFirstOperator = index + supportedOperators[opNdx].Length;
                            operatorsUsed.Add(supportedOperators[opNdx]);
                            break;
                        }
                    }
                }
            }

            if (indexOfFirstOperator < 0)
            {
                return(null); //operator not found...we must have extracted all instances in previous calls to this method
            }

            // grab value before operator
            index = indexOfFirstOperator - 1;
            while (index >= 0 && Char.IsWhiteSpace(calcDef_upperCase[index]))
            {
                index--;
            }
            int  indexOfValueBeforeFirstOperator = -1;
            int  indexAfterValueBeforeFirstOperatorIdentifier = index + 1;
            bool foundNonDigits = false;
            bool foundSomething = false;

            while (index >= 0)
            {
                if (Char.IsLetter(calcDef_upperCase[index]))
                {
                    foundSomething = true;
                    foundNonDigits = true;
                    index--;
                }
                else if (Char.IsDigit(calcDef_upperCase[index]))
                {
                    foundSomething = true;
                    index--;
                }
                else if (calcDef_upperCase[index] == '_')
                {
                    foundSomething = true;
                    foundNonDigits = true;
                    index--;
                }
                else if (calcDef_upperCase[index] == '#' && foundSomething && !foundNonDigits && (
                             (index == 0) || (index > 0 && Char.IsWhiteSpace(calcDef_upperCase[index - 1]))
                             )
                         )
                {
                    index--;
                }
                else
                {
                    break;
                }
            }
            if (!foundSomething)
            {
                throw new ArgumentException("Error parsing calculation. code=43987598");
            }
            indexOfValueBeforeFirstOperator = index + 1;
            string theValueIdentifier = calcDef.Substring(indexOfValueBeforeFirstOperator, indexAfterValueBeforeFirstOperatorIdentifier - indexOfValueBeforeFirstOperator);

            if (foundNonDigits)
            {
                if (!Char.IsLetter(theValueIdentifier[0]))
                {
                    throw new ArgumentException("Error parsing calculation: value identifier doesn't start with a letter: '" + theValueIdentifier + "'");
                }
            }

            if (!foundNonDigits && theValueIdentifier[0] == '#')
            {
                valueObjects.Add(calcToolDef.GetValueForAlias(theValueIdentifier));
            }
            else
            {
                valueObjects.Add(calcToolDef.TestSequence().DataValueRegistry.GetObject(theValueIdentifier));
            }

            // grab value after operator
            index = indexAfterFirstOperator;
            valueObjects.Add(GrabNextValue(calcToolDef, ref calcDef, ref index));

            // while next token is a matching operator, grab next value and check next token
            string theOperator;

            do
            {
                theOperator = GrabOperator(ref calcDef_upperCase, ref index);
                if (theOperator.Length > 0)
                {
                    operatorsUsed.Add(theOperator);
                    valueObjects.Add(GrabNextValue(calcToolDef, ref calcDef, ref index));
                }
            } while (theOperator != String.Empty);

            // remove substring for parsing and replace it with alias
            int    lengthOfSubString = index < calcDef_upperCase.Length ? index - indexOfValueBeforeFirstOperator : calcDef_upperCase.Length - indexOfValueBeforeFirstOperator;
            string subExpression     = calcDef.Substring(indexOfValueBeforeFirstOperator, lengthOfSubString).Trim();

            string calcDefBeforeAlias       = "";
            string calcDefBeforeAlias_upper = "";

            if (indexOfValueBeforeFirstOperator > 0)
            {
                calcDefBeforeAlias       = calcDef.Substring(0, indexOfValueBeforeFirstOperator).Trim();
                calcDefBeforeAlias_upper = calcDef_upperCase.Substring(0, indexOfValueBeforeFirstOperator).Trim();
                if (calcDefBeforeAlias.Length > 0)
                {
                    calcDefBeforeAlias       += ' ';
                    calcDefBeforeAlias_upper += ' ';
                }
            }
            string calcDefAfterAlias       = "";
            string calcDefAfterAlias_upper = "";

            if (index < calcDef_upperCase.Length)
            {
                calcDefAfterAlias       = calcDef.Substring(index).Trim();
                calcDefAfterAlias_upper = calcDef_upperCase.Substring(index).Trim();
                if (calcDefAfterAlias.Length > 0)
                {
                    calcDefAfterAlias       = ' ' + calcDefAfterAlias;
                    calcDefAfterAlias_upper = ' ' + calcDefAfterAlias_upper;
                }
            }
            if (calcDefBeforeAlias.Length > 0 || calcDefAfterAlias.Length > 0)
            {
                string           alias     = "#" + (calcToolDef.aliasCount++);
                CalculationAlias aliasGuts = new CalculationAlias();
                aliasGuts.expressionToLookupValue = subExpression;
                aliasGuts.expressionForExpansion  = "(" + subExpression + ")";
                calcToolDef.aliasMap.Add(alias, aliasGuts);
                calcDef           = calcDefBeforeAlias + alias + calcDefAfterAlias;
                calcDef_upperCase = calcDefBeforeAlias_upper + alias + calcDefAfterAlias_upper;
                if (calcDef.Length != calcDef_upperCase.Length)
                {
                    throw new ArgumentException("Error parsing calculation. code=293084");
                }
            }
            else
            {
                // nothing left to parse
                calcDef           = "";
                calcDef_upperCase = "";
            }

            // return calculation object
            string expandedCalcDef = calcToolDef.ExpandCalcDef(subExpression);

            return(CreateMathOperation(calcToolDef.TestSequence(), expandedCalcDef, valueObjects, operatorsUsed));
        }
Ejemplo n.º 2
0
        private MathOperationDefinition processDef(string calcDef)
        {
            MathOperationDefinition lastMathOpDef = null;

            // TODO: tell every MathOperation we create/use that we are referencing it.  Then when we stop referencing it, tell it so it can dispose itself if unused
            // TODO: sanitize every calculation def to optimize reuse
            // TODO: handle negative constants (e.g. "x * -2")

            // determine if mixed-operator expression; if so, determine all operators used so that we can sort by precedence
            List <MathOperationCreator>    operators           = new List <MathOperationCreator>();
            SumOperationCreator            sumOpCreator        = null;
            MultiplicationOperationCreator multOpCreator       = null;
            RelationalOperationCreator     relationalOpCreator = null;
            EqualityOperationCreator       equalityOpCreator   = null;
            LogicalAndOperationCreator     logicalAndOpCreator = null;
            LogicalOrOperationCreator      logicalOrOpCreator  = null;

            // strip out user-provided parathesis and determine operators used
            // * user-provided paranthesis are processed recursively and replaced with aliases
            // * since subexpressions wrapped with user-provided paranthesis are removed (replaced with aliases), we will end up with only the outer-most expression...and the operators within it...from here we process the operators by precedence
            // * operators, functions, etc within the subexpressions are handled by a recursive call
            bool lastTokenWasPossibleFunctionName = false; // checked when we find a open paranthesis
            int  x = 0;

            while (x < calcDef.Length)
            {
                if (calcDef[x] == '(')
                {
                    // TODO: check if this is a function (e.g. Sin, Tan, Max, Min, Abs, Iff) or just ()'s; is it preceeded by operator or name?

                    int startPos = x;
                    int endPos   = -1;
                    x++;                                     // move past '('
                    int nestedCount = 0;
                    while (x < calcDef.Length && endPos < 0) // find the matching closing paranthesis
                    {
                        if (calcDef[x] == '(')
                        {
                            nestedCount++;
                        }
                        if (calcDef[x] == ')')
                        {
                            if (nestedCount == 0)
                            {
                                endPos = x;
                            }
                            else
                            {
                                nestedCount--;
                            }
                        }
                        x++;
                    }
                    if (endPos < 0)
                    {
                        throw new ArgumentException("Missing a closing paranthesis");
                    }
                    else
                    {
                        string partBeforeAlias = "";
                        string partAfterAlias  = "";
                        string subExpression   = calcDef.Substring(startPos + 1, endPos - startPos - 1).Trim();

                        int                startOfFunctionName = 0;
                        string             functionName        = string.Empty;
                        FunctionDefinition functionDef         = null;

                        if (lastTokenWasPossibleFunctionName)
                        {
                            // find token before '('
                            bool foundStartOfToken = false;
                            bool foundToken        = false;
                            for (int y = startPos - 1; y > 0 && !foundStartOfToken; y--)
                            {
                                if (Char.IsWhiteSpace(calcDef[y]))
                                {
                                    if (!foundToken)
                                    {
                                        // do nothing...skip whitespace while we look for end of token (before open paran)
                                    }
                                    else
                                    {
                                        foundStartOfToken   = true;
                                        startOfFunctionName = y + 1;
                                    }
                                }
                                else
                                {
                                    foundToken = true;
                                }
                            }
                            functionName = calcDef.Substring(startOfFunctionName, startPos - startOfFunctionName).Trim();
                            startPos     = startOfFunctionName;
                        }

                        if (startPos > 0)
                        {
                            partBeforeAlias = calcDef.Substring(0, Math.Max(startPos - 1, 0)).Trim();
                            if (partBeforeAlias.Length > 0)
                            {
                                partBeforeAlias += ' ';
                            }
                        }
                        if (endPos < calcDef.Length - 1)
                        {
                            partAfterAlias = calcDef.Substring(endPos + 1, calcDef.Length - endPos - 1).Trim();
                            if (partAfterAlias.Length > 0)
                            {
                                partAfterAlias = ' ' + partAfterAlias;
                            }
                        }

                        if (functionName.Length > 0)
                        {
                            // if we're dealing with a Function call, split subexpression up by commas
                            // TODO: add support for nested Function calls by searching for comma's rather than using split (ie commas from nested function calls can't be treated at this level) (if we find an open paran, ignore commas until matching closing paran)
                            string[] functionParameterSubExpressions = subExpression.Split(new char[] { ',' });
                            List <DataValueDefinition> valueObjects  = new List <DataValueDefinition>();
                            foreach (string parameter in functionParameterSubExpressions)
                            {
                                string parameterExpression       = parameter.Trim();
                                DataValueDefinition dataValueDef = TestSequence().DataValueRegistry.GetObjectIfExists(parameterExpression);
                                if (dataValueDef == null)
                                {
                                    MathOperationDefinition nestedCalculationOp;
                                    try
                                    {
                                        nestedCalculationOp = processDef(parameterExpression);
                                    }
                                    catch (Exception e)
                                    {
                                        throw new ArgumentException("Unable to parse function call parameter '" + parameterExpression + "' for function call '" + functionName + "'; error = " + e.Message);
                                    }
                                    dataValueDef = nestedCalculationOp.Result;
                                }
                                valueObjects.Add(dataValueDef);
                            }
                            string functionCallsFingerPrint = functionName + "(" + subExpression + ")";
                            // lookup token as Function Call
                            switch (functionName.ToUpper()) // TODO: look up functions in table, then have a common creator method
                            {
                            case "MIN":
                                functionDef = new MinMaxFunctionDefinition(TestSequence(), functionCallsFingerPrint, MinMaxFunctionDefinition.Function.Min, valueObjects);
                                break;

                            case "MAX":
                                functionDef = new MinMaxFunctionDefinition(TestSequence(), functionCallsFingerPrint, MinMaxFunctionDefinition.Function.Max, valueObjects);
                                break;

                            case "ABS":
                                functionDef = new AbsFunctionDefinition(TestSequence(), functionCallsFingerPrint, valueObjects);
                                break;

                            default:
                                break;
                            }

                            if (partBeforeAlias.Length > 0 || partAfterAlias.Length > 0)
                            {
                                string           alias     = "#" + (aliasCount++);
                                CalculationAlias aliasGuts = new CalculationAlias();
                                aliasGuts.expressionToLookupValue = functionCallsFingerPrint;
                                aliasGuts.expressionForExpansion  = functionCallsFingerPrint;
                                aliasMap.Add(alias, aliasGuts);
                                calcDef = partBeforeAlias + alias + partAfterAlias;
                                x       = partBeforeAlias.Length;
                            }
                            else
                            {
                                lastMathOpDef = functionDef;
                                return(lastMathOpDef);
                            }
                        }
                        else // just user-provided paranthesis rather than a function call...
                        {
                            if (partBeforeAlias.Length > 0 || partAfterAlias.Length > 0)
                            {
                                string           alias     = "#" + (aliasCount++);
                                CalculationAlias aliasGuts = new CalculationAlias();
                                aliasGuts.expressionToLookupValue = subExpression;
                                aliasGuts.expressionForExpansion  = "(" + subExpression + ")";
                                aliasMap.Add(alias, aliasGuts);
                                calcDef = partBeforeAlias + alias + partAfterAlias;
                                x       = partBeforeAlias.Length; // added 5/3/08...we stripped off the first ()'s, now we should search the rest of the calc def...right???
                                processDef(subExpression);
                            }
                            else
                            {
                                calcDef = subExpression; // strip off ()'s
                                x       = 0;             // added 5/3/08...we stripped off the outer most ()'s, now we should search again...right???
                            }
                        }
                    }
                    lastTokenWasPossibleFunctionName = false;
                }
                else if (calcDef[x] == '+' || calcDef[x] == '-') // TODO: look for unary sign operator, eg "* -x", "* -42", "/ -42", etc (minus followed by no space?  preceded by another operator?...or start of calc)
                {
                    if (sumOpCreator == null)
                    {
                        sumOpCreator = SumOperationCreator.Singleton;
                        operators.Add(sumOpCreator);
                    }
                    lastTokenWasPossibleFunctionName = false;
                }
                else if (calcDef[x] == '*' || calcDef[x] == '/')
                {
                    if (multOpCreator == null)
                    {
                        multOpCreator = MultiplicationOperationCreator.Singleton;
                        operators.Add(multOpCreator);
                    }
                    lastTokenWasPossibleFunctionName = false;
                }
                // TODO: search for ^ and replace "value ^ value" with alias
                else if (calcDef[x] == '<' || calcDef[x] == '>')
                {
                    if (relationalOpCreator == null)
                    {
                        relationalOpCreator = RelationalOperationCreator.Singleton;
                        operators.Add(relationalOpCreator);
                    }
                    lastTokenWasPossibleFunctionName = false;
                }
                else if (calcDef[x] == '=' && x > 0 && (calcDef[x - 1] == '=' || calcDef[x - 1] == '!'))
                {
                    if (relationalOpCreator == null)
                    {
                        equalityOpCreator = EqualityOperationCreator.Singleton;
                        operators.Add(equalityOpCreator);
                    }
                    lastTokenWasPossibleFunctionName = false;
                }
                else if (calcDef[x] == '&' && x > 0 && calcDef[x - 1] == '&')
                {
                    if (logicalAndOpCreator == null)
                    {
                        logicalAndOpCreator = LogicalAndOperationCreator.Singleton;
                        operators.Add(logicalAndOpCreator);
                    }
                    lastTokenWasPossibleFunctionName = false;
                }
                else if (calcDef[x] == '|' && x > 0 && calcDef[x - 1] == '|')
                {
                    if (logicalOrOpCreator == null)
                    {
                        logicalOrOpCreator = LogicalOrOperationCreator.Singleton;
                        operators.Add(logicalOrOpCreator);
                    }
                    lastTokenWasPossibleFunctionName = false;
                }
                else if (Char.IsWhiteSpace(calcDef[x]))
                {
                    if (calcDef.Length > x + 4 && (calcDef[x + 1] == 'A' || calcDef[x + 1] == 'a') && (calcDef[x + 2] == 'N' || calcDef[x + 2] == 'n') && (calcDef[x + 3] == 'D' || calcDef[x + 3] == 'd') && Char.IsWhiteSpace(calcDef[x + 4]))
                    {
                        if (logicalAndOpCreator == null)
                        {
                            logicalAndOpCreator = LogicalAndOperationCreator.Singleton;
                            operators.Add(logicalAndOpCreator);
                        }
                        x += 4; // skip "AND "
                        lastTokenWasPossibleFunctionName = false;
                    }
                    else if (calcDef.Length > x + 3 && (calcDef[x + 1] == 'O' || calcDef[x + 1] == 'o') && (calcDef[x + 2] == 'R' || calcDef[x + 2] == 'r') && Char.IsWhiteSpace(calcDef[x + 3]))
                    {
                        if (logicalOrOpCreator == null)
                        {
                            logicalOrOpCreator = LogicalOrOperationCreator.Singleton;
                            operators.Add(logicalOrOpCreator);
                        }
                        x += 3; // skip "OR "
                        lastTokenWasPossibleFunctionName = false;
                    }
                }
                else
                {
                    lastTokenWasPossibleFunctionName = true;
                }

                x++;
            }


            // TODO: look for "* -x", "* -42", "/ -42", "+ -42", etc  NOTE: we don't assume everything is separated by whitespace, but operators must be...so look for 2 in a row

            string calcDef_upperCase = calcDef.ToUpper();

            // HANDLE PRECIDENCE
            // sort all operators that are used by precedence
            operators.Sort(MathOperationPrecedenceComparer.Singleton);

            // pull out (with alias) each highest-precidence component until all have been handled
            MathOperationDefinition mathOpDef;

            while (operators.Count > 0 && calcDef.Length > 0)
            {
                mathOpDef = operators[0].CreateMathOperation(this, ref calcDef, ref calcDef_upperCase);
                if (mathOpDef == null)
                {
                    operators.RemoveAt(0);
                }
                else
                {
                    lastMathOpDef = mathOpDef;
                }
            }
            if (lastMathOpDef == null)
            {
                throw new ArgumentException("Invalid calculation");
            }
            return(lastMathOpDef);
        }