/// <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()); } } }
/// <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); }
/// <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); }
// Как потом раздать результат в другие переменные? Ввести в формулы термин "[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); }
/// <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; }
/// <summary> /// Этот конструктор используется при неудачной обработке формулы, когда один из токенов содержит ошибку /// </summary> /// <param name="token">токен с ошибкой</param> public FormulaComputeResult(ICalcToken token) { _formulaCheckResult = token.Error; _errorBeginPositionInFormulaText = token.Position; _errorLengthInFormulaText = token.GetTokenTextLength(); }
/// <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); }
/// <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 }); }
/// <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 }); }