синтаксический анализатор мат.формул

From
Boris Rudakov (2:5054/9.4)
To
George Shuklin (2:5054/9@fidonet)
Date
2000-02-27T12:23:43Z
Area
RU.ALGORITHMS
Hello George!

26 Feb 00 09:38, George Shuklin wrote to Svirskiy Taras:

 GS> А, это ты Svirskiy! Вот что я тебе сказать хотел...
 GS>   В 25 февраля 2000 01:12, Svirskiy Taras решил написать All:
 ST>> Может кто-нибудь знает, или писал  алгоритм сабжа,
 ST>> или где об этом вообще почитать можно.
 ST>> Плиз, пмомогите, сильно надо.
 GS> Вот, лови. Не пинать. Писано в свое время для сдачи по ПОВС, посему
 GS> возможны глюки.
[...]

Плохо :)

Такие вещи должны решаться элементарнейшим нисходящим разбором, если не являются частью какого-то более крупного языка, тогда разбор выражений для общности выполняется по единому алгоритму транслятора.

Вот слегка покоцанный примерчик калькулятора:
(примечания:
* Декларации типов данных я не привожу - большие и к делу мало относятся
* Разбор числовых литералов (чисел) я не привожу, но тут использована одна
  функция из другой библиотеки. Можно заменить на что угодно.
* Оригинал абсолютно рабочий и активно используется около года :)
)

=== Cut ===
/////////////////////////////////////////////////////////////////////////////// /
// Function definitions //
/////////////////////////////////////////////////////////////////////////////// /

#ifdef ISRTLWIN32
#  define NEARPROC __fastcall
#elif defined(ISRTLWIN16)
#  define NEARPROC __near __fastcall
#else
#  define NEARPROC
#endif

typedef bool (NEARPROC *TFnProc)(LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr);

bool NEARPROC FnAbs    (LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr);
bool NEARPROC FnACos   (LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr);
//...Покоцано
bool NEARPROC FnSum    (LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr);
bool NEARPROC FnTan    (LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr);

/////////////////////////////////////////////////////////////////////////////// /
// Reserved words table //
/////////////////////////////////////////////////////////////////////////////// /

typedef struct TFnDesc {
  LPCSTR Name;
  const GUID FAR* GrpGUID;
  TFnProc Proc;
} TFnDesc, FAR* PFnDesc;

static TFnDesc ResWords[] = {
  {"\3abs",   &IID_ICalcArithmGrp, FnAbs},
  {"\4acos",  &IID_ICalcTrigonGrp, FnACos},
//...Покоцано
  {"\3sum",   &IID_ICalcVectorGrp, FnSum},
  {"\3tan",   &IID_ICalcTrigonGrp, FnTan},
};

int LOCALPROC SearchResWord(LPCSTR Word, int Len)
{
  const int Count = CountOf(ResWords);
  int Hi = Count - 1, Lo = 0;
  int Mid, Cmp, L;

  while (Lo <= Hi) {
    Mid = (Hi + Lo) >> 1;
    L = min((int)ResWords[Mid].Name[0], (int)Len);
    Cmp = MemCmp((LPVOID)Word, (LPVOID)&ResWords[Mid].Name[1], L);
    if (Cmp == 0) Cmp = Len - (int)ResWords[Mid].Name[0];
    if (Cmp > 0) Lo = Mid + 1;
    else if (Cmp < 0) Hi = Mid - 1;
    else return Mid;
  }
  return -1;
}

/////////////////////////////////////////////////////////////////////////////// /
// Parser //
/////////////////////////////////////////////////////////////////////////////// /

void LOCALPROC SkipWhite(LPCSTR FAR& Str)
{
  LPCSTR S = Str;
  while (*S == ' ' || *S == 9 || *S == 13 || *S == 10) S++;
  Str = S;
}

bool LOCALPROC SkipTo(LPCSTR FAR& Str, char To, TError Error, TCalcExpr FAR& Expr)
{
  SkipWhite(Str);
  if (*Str == To) {
    Str++;
    return true;
  } else {
    SetError(Error, Str, Expr);
    return false;
  }
}

bool LOCALPROC GetTerm(LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr);
bool LOCALPROC GetMult(LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr);
bool LOCALPROC GetExpr(LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr);

bool LOCALPROC ParseWord(LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr)
{
  LPCSTR S = Str;
  LPCSTR SS = Str;
  char Word[24];
  LPSTR W = Word;
  int L;
  while (TRUE) {
    if ((L = (W - Word)) >= sizeof(Word)) {
      SetError(erNoResWord, S, Expr);
      return false;
    }
    if (*S < 'A') break;
    else *W++ = *S++;
  }
  CharLowerBuffA(Word, L);
  int Fn = SearchResWord(Word, L);
  if (Fn == -1) {
    SetError(erNoResWord, SS, Expr);
    return false;
  }
  Str = S;
  return ResWords[Fn].Proc(Str, Val, Expr);
}

bool LOCALPROC GetTerm(LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr)
{
  LPCSTR S = Str;
  double V = 0;
Again:
  SkipWhite(S);
  if (*S == '-') {
    S++;
    if (!GetTerm(S, V, Expr)) return false;
    V = -V;
  } else if (*S == '+') {
    S++;
    while (*S == '+') S++;
    goto Again;  // Знаю-знаю, но уж очень хотелось...
  } else if (*S == '(') {
    S++;
    if (!GetExpr(S, V, Expr)) return false;
    if (!SkipTo(S, ')', erRBrace, Expr)) return false;
  } else if (*S == '.' || (*S >= '0' && *S <= '9')) {
    V = StrToDoubleParseA(&S); // Call some standalone fn for parse numbers,
                               // may be used any sutable code
    if (GetLastError()) {      // Check parsing error
      SetError(erValue, S, Expr);
      return false;
    }
  } else if (*S)
    if (*S < 'A') {
      SetError(erNeedValue, S, Expr);
      return false;
    } else if (!ParseWord(S, V, Expr)) return false;
  Str = S;
  Val = V;
  return true;
}

bool LOCALPROC GetMult(LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr)
{
  LPCSTR S = Str;
  double V1 = 0, V2;
  if (!GetTerm(S, V1, Expr)) return false;
  while (*S) {
    SkipWhite(S);
    if (*S == '*') {
      S++;
      bool Pow = *S == '*';
      if (Pow) S++;
      if (!GetTerm(S, V2, Expr)) return false;
      if (Pow) V1 = pow(V1, V2);
      else V1 = V1 * V2;
    } else if (*S == '/') {
      S++;
      if (!GetTerm(S, V2, Expr)) return false;
      Str = S;       // Temporary solution for exception handler
      V1 = V1 / V2;  // Do not check for "/ 0" - will catch exception instead
    } else if (*S == '%') {
      S++;
      if (!GetTerm(S, V2, Expr)) return false;
      V1 = fmod(V1, V2);
    } else break;
  }
  Str = S;
  Val = V1;
  return true;
}

bool LOCALPROC GetExpr(LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr)
{
  LPCSTR S = Str;
  double V1 = 0, V2;
  if (!GetMult(S, V1, Expr)) return false;
  while (*S) {
    SkipWhite(S);
    if (*S == '+') {
      S++;
      if (!GetMult(S, V2, Expr)) return false;
      V1 = V1 + V2;
    } else if (*S == '-') {
      S++;
      if (!GetMult(S, V2, Expr)) return false;
      V1 = V1 - V2;
    } else break;
  }
  Str = S;
  Val = V1;
  return true;
}

/////////////////////////////////////////////////////////////////////////////// /
// TCalcExpr //
/////////////////////////////////////////////////////////////////////////////// /

#ifdef __BORLANDC__
#  include <float.h>
#endif

int LOCALPROC RegisterCalcException(DWORD Code, LPCSTR S, TCalcExpr FAR& Expr)
{
  TError Kind;
  switch (Code) {
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
      Kind = erZeroDivide;
      break;
    case EXCEPTION_FLT_OVERFLOW:
    case EXCEPTION_INT_OVERFLOW:
      Kind = erOverflow;
      break;
    case EXCEPTION_FLT_UNDERFLOW:
      Kind = erUnderflow;
      break;
    default:
      Kind = erSystem;
      break;
  }
#ifdef __BORLANDC__
//
// Reset FPU unit because of very strange program behavior after FPU errors.
//
  switch (Code) {
    case EXCEPTION_FLT_DENORMAL_OPERAND :
    case EXCEPTION_FLT_DIVIDE_BY_ZERO   :
    case EXCEPTION_FLT_INEXACT_RESULT   :
    case EXCEPTION_FLT_INVALID_OPERATION:
    case EXCEPTION_FLT_OVERFLOW         :
    case EXCEPTION_FLT_STACK_CHECK      :
      _fpreset();
      break;
  }
#elif !defined(__MSC)
#  pragma message Need to check FPU recovery after FPU errors !
#endif
  SetError(Kind, S, Expr);
  return EXCEPTION_EXECUTE_HANDLER;
}

// Собственно главный метод калькулятора (он у меня оформлен в виде // COM-объекта).
//
BOOL STDMETHODCALLTYPE TCalcExpr::Calculate()
{
  LPCSTR S = Formula;
  try {
    BOOL Result = GetExpr(S, Value, *this);
    if (Result && *S) {
      SetError(erNeedValue, S, *this);
      return FALSE;
    }
    return TRUE;
  } __except(RegisterCalcException(GetExceptionCode(), S, *this)) {
    return false;
  }
}

/////////////////////////////////////////////////////////////////////////////// /
// Functions implementation //
/////////////////////////////////////////////////////////////////////////////// /

bool NEARPROC FnAbs    (LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr)
{
  double V;
  if (!GetTerm(Str, V, Expr)) return false;
  Val = fabs(V);
  return true;
}

bool NEARPROC FnACos   (LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr)
{
  double V;
  if (!GetTerm(Str, V, Expr)) return false;
  Val = acos(V);
  return true;
}

// Покоцано...

bool NEARPROC FnSum    (LPCSTR FAR& Str, double FAR& Val, TCalcExpr FAR& Expr)
{
  LPCSTR S = Str;
  double V1, V2;
  if (!SkipTo(S, '(', erLBrace, Expr)) return false;
  if (!GetExpr(S, V1, Expr)) return false;
  SkipWhite(S);
  while (*S == ';') {
    S++;
    if (!GetExpr(S, V2, Expr)) return false;
    V1 += V2;
    SkipWhite(S);
  }
  if (!SkipTo(S, ')', erRBrace, Expr)) return false;
  Str = S;
  Val = V1;
  return true;
}

// Ну и ты-пы

=== Cut ===

Вот и все. Просто и со вкусом.

 GS> George.

Борис Рудаков,               Маю песню услышут ты-ся-чи ух !
BBR

--- Be happy: BBR is looking at you !
 * Origin: АлкАголь малыми дозами безвреден в любых количествах (2:5054/9.4)