ExpressionParser (анализатор выражений)
Шилдт, Герберт. Полный справочник по C#.: Пер. с англ. - М.: Издательский дом "Вильямс", 2004. - 752с.: ил. - Парал. тит. англ. ISBN 5-8459-0563-X (рус.)
Код:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace ExpressionParser
{
/// <summary>
/// Класс исключения для обнаружения ошибок анализатора
/// </summary>
class ParserException : ApplicationException
{
public ParserException(string str) : base(str) { }
public override string ToString()
{
return Message;
}
}
/// <summary>
/// Класс анализа математических выражений
/// </summary>
public class Parser
{
public Parser()
{
//
// TODO: Add constructor logic here
//
//for(int i=0; i< vars.Length; i++) vars[i] = 0.0;
mVars = new Dictionary<string, double>();
mShowErrorMessages = true;
}
/// <summary>
/// Массив переменных
/// </summary>
//double []vars = new double[26];
private Dictionary <String, double> mVars;
private bool mShowErrorMessages;
/// <summary>
/// Показывать ли сообщения об ошибках
/// </summary>
public bool ShowErrorMessages
{
get
{
return mShowErrorMessages;
}
set
{
mShowErrorMessages = value;
}
}
/// <summary>
/// Входная точка анализатора
/// </summary>
/// <param name="exprstr">Строка выражения</param>
/// <returns>Вычисленное значение</returns>
public double Evaluate(string exprstr)
{
double result = 0.0;
exp = exprstr;
expIdx = 0;
try
{
GetToken();
if (token == "")
{
SyntaxErr(Errors.NoExp);//выражение отсутствует
return 0.0;
}
EvalExp1(out result);
if (token != "")//Последняя лексема должна быть null-значением
SyntaxErr(Errors.Syntax);
return result;
}
catch (ParserException exc)
{
//Console.WriteLine(exc);
//MessageBox.Show("При вычислении выражения \"" + exprstr + "\"\nпроизошла ошибка \"" + exc,
// "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return Double.NaN;;
}
}
public void ClearVars()
{
mVars.Clear();
}
private string exp; //строка выражения
private int expIdx; //текущий индекс в выражении
private string token; //текущая лексема
private Types tokType; //тип лексемы
private void GetToken()
{
tokType = Types.None;
token = "";
if (expIdx == exp.Length) return;//конец выражения
//пропускаем пробелы
while (expIdx < exp.Length &&
Char.IsWhiteSpace(exp[expIdx])) ++expIdx;
//хвостовой пробел завершает выражение
if (expIdx == exp.Length) return;
if (IsDelim(exp[expIdx]))//если это оператор
{
token += exp[expIdx];
expIdx++;
tokType = Types.Delimiter;
}
else if (Char.IsLetter(exp[expIdx]))//если это переменная
{
while (!IsDelim(exp[expIdx]))
{
token += exp[expIdx];
expIdx++;
if (expIdx >= exp.Length) break;
}
tokType = Types.Variable;
}
else if (Char.IsDigit(exp[expIdx]))//если это число
{
while (!IsDelim(exp[expIdx]))
{
token += exp[expIdx];
expIdx++;
if (expIdx >= exp.Length) break;
}
tokType = Types.Number;
}
}
private bool IsDelim(char c)
{
if ((" +-/*%=()".IndexOf(c) != -1))
return true;
return false;
}
/// <summary>
/// Обработка присвоения
/// </summary>
/// <param name="result">Результат</param>
private void EvalExp1(out double result)
{
int varIdx;
Types ttokType;
string tempToken;
if (tokType == Types.Variable)
{
//сохранение старой лексемы
tempToken = String.Copy(token);
ttokType = tokType;
//вычисление индекса переменной
//varIdx = Char.ToUpper(token[0]) - 'A';
GetToken();
if (token != "=")
{
PutBack();//возвращение текущей лексемы в поток и восстановление старой, т.к. не было присвоения
token = String.Copy(tempToken);
tokType = ttokType;
}
else
{
GetToken();//получаем следующую часть выражения exp
EvalExp2(out result);
//vars[varIdx] = result;
if (!mVars.ContainsKey(tempToken))
{
mVars.Add(tempToken, result);
}
else
{
mVars[tempToken] = result;
}
return;
}
}
EvalExp2(out result);
}
/// <summary>
/// Сложение или вычитание двух членов выражения.
/// </summary>
/// <param name="result">Вычисленное значение</param>
private void EvalExp2(out double result)
{
string op;
double partialResult = 0.0;
EvalExp3(out result);
while ((op = token) == "+" || op == "-")
{
GetToken();
EvalExp3(out partialResult);
switch (op)
{
case "-":
result = result - partialResult;
break;
case "+":
result = result + partialResult;
break;
}
}
}
/// <summary>
/// Умножение или деление двух множителей
/// </summary>
/// <param name="result">Вычисленное значение</param>
private void EvalExp3(out double result)
{
string op;
double partialResult = 0.0;
EvalExp4(out result);
while ((op = token) == "*" || op == "/" || op == "%")
{
GetToken();
EvalExp4(out partialResult);
switch (op)
{
case "*":
result = result * partialResult;
break;
case "/":
if (partialResult == 0.0) SyntaxErr(Errors.DivByZero);
result = result / partialResult;
break;
case "%":
if (partialResult == 0.0) SyntaxErr(Errors.DivByZero);
result = (int)result % (int)partialResult;
break;
}
}
}
/// <summary>
/// Возведение в степень
/// </summary>
/// <param name="result">Вычисленное значение</param>
private void EvalExp4(out double result)
{
double partialResult, ex;
int t;
EvalExp5(out result);
if (token == "^")
{
GetToken();
EvalExp4(out partialResult);
ex = result;
if (partialResult == 0.0)
{
result = 1.0;
return;
}
for (t = (int)partialResult - 1; t > 0; t--)
result = result * (double)ex;
}
}
/// <summary>
/// Операция унарного + или -
/// </summary>
/// <param name="result">Вычисленное значение</param>
private void EvalExp5(out double result)
{
string op;
op = "";
if ((tokType == Types.Delimiter) && token == "+" || token == "-")
{
op = token;
GetToken();
}
EvalExp6(out result);
if (op == "-") result = -result;
}
/// <summary>
/// Обработка выражений в круглых скобках
/// </summary>
/// <param name="result">Вычисленное значение</param>
private void EvalExp6(out double result)
{
if (token == "(")
{
GetToken();
EvalExp2(out result);
if (token != ")")
SyntaxErr(Errors.Unbalparens);
GetToken();
}
else
Atom(out result);
}
/// <summary>
/// Получение значения числа
/// </summary>
/// <param name="result">Вычисленное значение</param>
private void Atom(out double result)
{
switch (tokType)
{
case Types.Number:
try
{
result = Double.Parse(token);
}
catch (FormatException)
{
result = 0.0;
SyntaxErr(Errors.Syntax);
}
GetToken();
break;
case Types.Variable:
result = FindVar(token);
GetToken();
break;
default:
result = 0.0;
SyntaxErr(Errors.Syntax);
break;
}
}
/// <summary>
/// Получение значения переменной
/// </summary>
/// <param name="vname">Имя переменной</param>
/// <returns>Значение переменной</returns>
private double FindVar(string vname)
{
if (!Char.IsLetter(vname[0]))
{
SyntaxErr(Errors.Syntax);
return 0.0;
}
if (!mVars.ContainsKey(vname))
{
SyntaxErr(Errors.VariableNotFound);
return 0.0;
}
//return vars[Char.ToUpper(vname[0]) - 'A'];
return mVars[vname];
}
private void PutBack()
{
for (int i = 0; i < token.Length; i++) expIdx--;
}
/// <summary>
/// Обработка синтаксических ошибок
/// </summary>
/// <param name="error"></param>
private void SyntaxErr(Errors error)
{
if (mShowErrorMessages)
{
string[] err = {
"Синтаксическая ошибка",
"Дисбаланс скобок",
"Выражение отсутствует",
"Деление на ноль",
"Переменная не найдена (значение не присвоено)"};
throw new ParserException(err[(int)error]);
}
}
}
/// <summary>
/// Типы лексем
/// </summary>
enum Types { None, Delimiter, Variable, Number };
/// <summary>
/// Типы ошибок
/// </summary>
enum Errors { Syntax, Unbalparens, NoExp, DivByZero, VariableNotFound };
}