예제 #1
0
        /// <summary>
        /// Разбор формулы на токены, пропуская форматирующие токены
        /// </summary>
        /// <param name="formula">текст формулы</param>
        /// <returns>разбор прошёл удачно, если в последнем токене код ошибки ErrorCode.Ok. Количество токенов всегда больше нуля</returns>
        private ICalcToken[] TokenizeFormulaSkipFormatters(string formula)
        {
            var        tokens    = new List <ICalcToken>();
            var        tokenId   = 0;
            ICalcToken lastToken = null;

            while (true)
            {
                lastToken = TryToExtract(formula, lastToken);
                // Разбор формулы окончен
                if (lastToken == null)
                {
                    return(tokens.ToArray());
                }

                // Добавляем полученный токен в список токенов
                if (!(lastToken is CalcTokenFormatter))
                {
                    tokens.Add(lastToken);
                    lastToken.Id = tokenId;
                    tokenId++;
                }

                // Если произошла ошибка при разборе формулы, заканчиваем разбор
                if (lastToken.Error != FormulaError.Ok)
                {
                    return(tokens.ToArray());
                }
            }
        }
예제 #2
0
        /// <summary>
        /// Попытка извлечь токен - математическую операцию
        /// </summary>
        /// <param name="formula">формула</param>
        /// <param name="previousToken">предыдущий токен</param>
        /// <param name="currentTokenPosition">позиция в формуле, с которой нужно начинать парсить токен</param>
        /// <returns>информация о токене, null - токен не обнаружен</returns>
        public static ICalcToken TryToExtract(string formula, ICalcToken previousToken, int currentTokenPosition)
        {
            var token = new CalcTokenMathOperation(currentTokenPosition);

            var formulaCut = formula.Remove(0, currentTokenPosition);

            foreach (var op in Enum.GetValues(typeof(CalcMathOperation)))
            {
                var operation = (CalcMathOperation)op;
                var isUnary   = previousToken == null || previousToken is CalcTokenMathOperation || previousToken is CalcTokenLogicOperation ||
                                (previousToken is CalcTokenBracket && (previousToken as CalcTokenBracket).Bracket == CalcBracket.Open);
                var operationText = ToText(operation, isUnary);
                if (operationText == null)
                {
                    continue;
                }
                if (!formulaCut.StartsWith(operationText))
                {
                    continue;
                }
                token.MathOperation = operation;
                token.TokenText     = operationText;
                return(token);
            }
            return(null);
        }
예제 #3
0
        /// <summary>
        /// Попытка извлечь токен-число
        /// </summary>
        /// <param name="formula">формула</param>
        /// <param name="previousToken">предыдущий токен</param>
        /// <param name="currentTokenPosition">позиция в формуле, с которой нужно начинать парсить токен</param>
        /// <returns>информация о токене, null - токен не обнаружен</returns>
        public static ICalcToken TryToExtract(string formula, ICalcToken previousToken, int currentTokenPosition)
        {
            var token = new CalcTokenNumber(currentTokenPosition);

            // Если начало текста токена, или следующий символ после знака минус не содержит цифр, значит это не число
            if (formula[currentTokenPosition] < '0' || formula[currentTokenPosition] > '9')
            {
                return(null);
            }

            for (var i = currentTokenPosition; i < formula.Length; i++)
            {
                if ((formula[i] < '0' || formula[i] > '9') && formula[i] != '.')
                {
                    break;
                }
                // Число не может содержать две точки
                if (formula[i] == '.' && token.TokenText.Contains("."))
                {
                    token.Error = FormulaError.MultipluDotInNumber;
                    break;
                }
                token.TokenText += formula[i];
            }
            // Число не может заканчиваться на точку
            if (token.TokenText[token.TokenText.Length - 1] == '.')
            {
                token.Error = FormulaError.DotCantBeLastSymbolOfNumber;
            }
            token.Value = Convert.ToDouble(token.TokenText, CultureInfo.InvariantCulture);
            return(token);
        }
예제 #4
0
        // Как потом раздать результат в другие переменные? Ввести в формулы термин "[R]"?
        private ICalcToken FormulaResultProcessor(ICalcToken tokenToPreprocess)
        {
            if (!(tokenToPreprocess is CalcTokenNumber))
            {
                return(tokenToPreprocess);
            }
            const string resultTokenText = "[R]";

            if (((CalcTokenNumber)tokenToPreprocess).TokenText == resultTokenText)
            {
                ((CalcTokenNumber)tokenToPreprocess).Value = _currentFormulaResultForTokenizer;
            }
            return(tokenToPreprocess);
        }
예제 #5
0
        /// <summary>
        /// Общий метод извлечения очередного токена. Вызывает метод TryToExtract в классах токенов
        /// </summary>
        /// <param name="formula">текст формулы</param>
        /// <param name="previousToken">предыдущий токен. Null, если это первый токен</param>
        /// <returns>null - формула разобрана, иначе токен</returns>
        private ICalcToken TryToExtract(string formula, ICalcToken previousToken)
        {
            ICalcToken token;
            var        position = previousToken == null ? 0 : previousToken.GetNextTokenPosition();

            if (formula.Length == position)
            {
                return(null); // Формула распаршена
            }
            if ((token = CalcTokenIfStatement.TryToExtract(formula, position)) != null)
            {
                return(token);
            }
            if ((token = CalcTokenFormulaSeparator.TryToExtract(formula, position)) != null)
            {
                return(token);
            }
            if ((token = CalcTokenNumber.TryToExtract(formula, previousToken, position)) != null)
            {
                return(token);
            }
            if ((token = CalcTokenFormatter.TryToExtract(formula, position)) != null)
            {
                return(token);
            }
            if ((token = CalcTokenBracket.TryToExtract(formula, position)) != null)
            {
                return(token);
            }
            if ((token = CalcTokenLogicOperation.TryToExtract(formula, position)) != null)
            {
                return(token);
            }
            if ((token = CalcTokenMathOperation.TryToExtract(formula, previousToken, position)) != null)
            {
                return(token);
            }
            if (_tokenizers.Any(tokenizer => (token = tokenizer(formula, position)) != null))
            {
                return(token);
            }

            return(new CalcTokenUnknown(position)
            {
                Error = FormulaError.UnexpectedSymbols, TokenText = formula[position].ToString(CultureInfo.InvariantCulture)
            });
        }
 /// <summary>
 /// Метод для доступа к значению переменной по токену в формуле
 /// </summary>
 /// <param name="tokenToPreprocess">Токен, в котором есть ссылка на переменную, null, если не удалось обработать</param>
 /// <returns>Токен с добавленным значением переменной в поле Value</returns>
 public ICalcToken VariablePreprocessor(ICalcToken tokenToPreprocess)
 {
     if (!(tokenToPreprocess is CalcTokenNumber))
         return tokenToPreprocess;
     var text = ((CalcTokenNumber)tokenToPreprocess).TokenText;
     if (!text.Contains('[') || !text.Contains(']') || !text.Contains('.'))
         return tokenToPreprocess;
     var varAndPanelName = text.Substring(1, text.Length - 2).Split('.');
     if (varAndPanelName.Length != 2)
         return null;
     var varId = Profile.GetVariableByPanelAndName(varAndPanelName[0], varAndPanelName[1]);
     if (varId == Guid.Empty)
         return tokenToPreprocess;
     var readResult = _readCachedValues ? Profile.VariableStorage.ReadCachedValue(varId) : Profile.VariableStorage.ReadValue(varId);
     if (readResult.Error != ProcessVariableError.Ok)
         return tokenToPreprocess;
     ((CalcTokenNumber)tokenToPreprocess).Value = readResult.Value;
     return tokenToPreprocess;
 }
예제 #7
0
 /// <summary>
 /// Этот конструктор используется при неудачной обработке формулы, когда один из токенов содержит ошибку
 /// </summary>
 /// <param name="token">токен с ошибкой</param>
 public FormulaComputeResult(ICalcToken token)
 {
     _formulaCheckResult = token.Error;
     _errorBeginPositionInFormulaText = token.Position;
     _errorLengthInFormulaText        = token.GetTokenTextLength();
 }
예제 #8
0
        /// <summary>
        /// Получить приоритет математической/логической операции
        /// </summary>
        /// <param name="calcTokenBase">Токенизированная формула</param>
        /// <returns>Приоритет операции</returns>
        private int GetTokenPriority(ICalcToken calcTokenBase)
        {
            if (calcTokenBase is CalcTokenFormatter || calcTokenBase is CalcTokenBoolean ||
                calcTokenBase is CalcTokenNumber || calcTokenBase is CalcTokenUnknown)
            {
                return(0);
            }

            if (calcTokenBase is CalcTokenBracket)
            {
                if (((CalcTokenBracket)calcTokenBase).Bracket == CalcBracket.Close)
                {
                    return(1);
                }
            }
            if (calcTokenBase is CalcTokenLogicOperation)
            {
                var t = (CalcTokenLogicOperation)calcTokenBase;
                if (t.LogicOperation == CalcLogicOperation.Equal ||
                    t.LogicOperation == CalcLogicOperation.Greater ||
                    t.LogicOperation == CalcLogicOperation.Less ||
                    t.LogicOperation == CalcLogicOperation.LessOrEqual ||
                    t.LogicOperation == CalcLogicOperation.GreaterOrEqual
                    )
                {
                    return(4);
                }
                if (t.LogicOperation == CalcLogicOperation.And ||
                    t.LogicOperation == CalcLogicOperation.Or ||
                    t.LogicOperation == CalcLogicOperation.Not
                    )
                {
                    return(3);
                }
            }
            if (calcTokenBase is CalcTokenMathOperation)
            {
                var t = (CalcTokenMathOperation)calcTokenBase;
                if (t.MathOperation == CalcMathOperation.UnaryPlus ||
                    t.MathOperation == CalcMathOperation.UnaryMinus
                    )
                {
                    return(10);
                }
                if (t.MathOperation == CalcMathOperation.Multiply ||
                    t.MathOperation == CalcMathOperation.Divide ||
                    t.MathOperation == CalcMathOperation.DivideModulo ||
                    t.MathOperation == CalcMathOperation.DivideInteger
                    )
                {
                    return(8);
                }
                if (t.MathOperation == CalcMathOperation.Plus ||
                    t.MathOperation == CalcMathOperation.Minus
                    )
                {
                    return(6);
                }
            }
            return(0);
        }
예제 #9
0
        /// <summary>
        /// Обработать логическую операцию над двумя логическими токенами (bool) или двумя числами
        /// Для корректного выполнения сравнения "больше, меньше, ..." важен порядок токенов, передаваемых в формулу
        /// </summary>
        /// <param name="token1">первый токен</param>
        /// <param name="token2">второй токен</param>
        /// <param name="logicOperationToken">токен логической операции</param>
        /// <returns>результирующий токен или ошибка, установленная методом в одном из входных токенов</returns>
        private ICalcToken ProcessLogicOperation(ICalcToken token1, ICalcToken token2, CalcTokenLogicOperation logicOperationToken)
        {
            bool boolResult;

            if (token1 is CalcTokenNumber && token2 is CalcTokenNumber)
            {
                var v1Value = ((CalcTokenNumber)token1).Value;
                var v2Value = ((CalcTokenNumber)token2).Value;
                switch (logicOperationToken.LogicOperation)
                {
                case CalcLogicOperation.Equal:
                    boolResult = v1Value == v2Value;
                    break;

                case CalcLogicOperation.Greater:
                    boolResult = v1Value > v2Value;
                    break;

                case CalcLogicOperation.GreaterOrEqual:
                    boolResult = v1Value >= v2Value;
                    break;

                case CalcLogicOperation.Less:
                    boolResult = v1Value < v2Value;
                    break;

                case CalcLogicOperation.LessOrEqual:
                    boolResult = v1Value <= v2Value;
                    break;

                case CalcLogicOperation.Not:
                    boolResult = v1Value != v2Value;
                    break;

                default:
                    logicOperationToken.Error = FormulaError.UnknownLogicOperation;
                    return(logicOperationToken);
                }
            }
            else if (token1 is CalcTokenBoolean && token2 is CalcTokenBoolean)
            {
                var v1Value = ((CalcTokenBoolean)token1).Value;
                var v2Value = ((CalcTokenBoolean)token2).Value;
                switch (logicOperationToken.LogicOperation)
                {
                case CalcLogicOperation.And:
                    boolResult = v1Value && v2Value;
                    break;

                case CalcLogicOperation.Equal:
                    boolResult = v1Value == v2Value;
                    break;

                case CalcLogicOperation.Not:
                    boolResult = v1Value != v2Value;
                    break;

                case CalcLogicOperation.Or:
                    boolResult = v1Value || v2Value;
                    break;

                default:
                    logicOperationToken.Error = FormulaError.UnknownLogicOperation;
                    return(logicOperationToken);
                }
            }
            else
            {
                return(new CalcTokenUnknown(0)
                {
                    Error = FormulaError.CantOperateMathAndLogicValues
                });
            }
            return(new CalcTokenBoolean(0)
            {
                Value = boolResult
            });
        }
예제 #10
0
        /// <summary>
        /// Обработать математическую операцию над двумя токенами
        /// Для корректного выполнения сравнения "больше, меньше, ..." важен порядок токенов, передаваемых в формулу
        /// </summary>
        /// <param name="token1">первый токен</param>
        /// <param name="token2">второй токен</param>
        /// <param name="mathOperationToken">токен матеметической операции</param>
        /// <returns>результирующий токен или ошибка, установленная методом в одном из входных токенов</returns>
        private ICalcToken ProcessMathOperation(ICalcToken token1, ICalcToken token2, CalcTokenMathOperation mathOperationToken)
        {
            double mathResult;
            var    n1 = ((CalcTokenNumber)token1).Value;
            var    n2 = ((CalcTokenNumber)token2).Value;

            var n1s = n1.ToString(CultureInfo.InvariantCulture);

            n1 = double.Parse(n1s, CultureInfo.InvariantCulture);
            var n2s = n2.ToString(CultureInfo.InvariantCulture);

            n2 = double.Parse(n2s, CultureInfo.InvariantCulture);

            switch (mathOperationToken.MathOperation)
            {
            case CalcMathOperation.Plus:
                mathResult = n1 + n2;
                break;

            case CalcMathOperation.Minus:
                mathResult = n1 - n2;
                break;

            case CalcMathOperation.Multiply:
                mathResult = n1 * n2;
                break;

            case CalcMathOperation.Divide:
                if ((int)n2 == 0)
                {
                    token2.Error = FormulaError.DivisionByZero;
                    return(token2);
                }
                mathResult = n1 / n2;
                break;

            case CalcMathOperation.DivideModulo:
                if ((int)n2 == 0)
                {
                    token2.Error = FormulaError.DivisionByZero;
                    return(token2);
                }
                mathResult = n1 % n2;
                break;

            case CalcMathOperation.DivideInteger:
                if ((int)n2 == 0)
                {
                    token2.Error = FormulaError.DivisionByZero;
                    return(token2);
                }
                mathResult = (long)(n1 / n2);
                break;

            default:
                mathOperationToken.Error = FormulaError.UnknownMathOperation;
                return(mathOperationToken);
            }
            // Результат возвращаем в стэк
            return(new CalcTokenNumber(0)
            {
                Value = mathResult
            });
        }