Добавление условных выражений и функций в математический парсер - PullRequest
2 голосов
/ 16 июля 2010

У меня есть встроенный синтаксический анализатор математических выражений на основе двоичного дерева, который отлично работает для «нормальной» математики, например: (3.5 * 2) ^ 1 / (1 << 6). Тем не менее, я хотел бы немного расширить его, добавив троичный оператор выбора, отражающий оператор из C: {expr} ? {true-expr} : {false-expr}. Я также хотел бы добавить функции, такие как sin(x) или ave(...).

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

Мой текущий синтаксический анализатор работает, вычисляя инфиксное выражение и немедленно преобразуя его в дерево, а затем оттуда можно вычислить дерево, т. Е. Его дерево стандартных выражений.

В настоящее время мой оценщик выглядит так:

struct Node
{
    int nType;
    union
    {
        unsigned long dwOperator;
        BOOL bValue;
        int nValue; //for indices, args & functions
        number_t fValue;
        char* szValue; //for string literals to pass to functions
    };

    Node* pLeft;
    Node* pRight;
};

number_t EvaluateTree(Node* pNode)
{
    if(pNode == NULL)
        return 0.0f;

    int nType = pNode->nType;
    if(nType == TOKEN_OPERATOR)
    {
        number_t fLeft = EvaluateTree(pNode->pLeft);
        number_t fRight = EvaluateTree(pNode->pRight);
        switch(pNode->dwOperator)
        {
            case '+': return fLeft + fRight;
            case '-': return fLeft - fRight;
            case '*': return fLeft * fRight;
            case '/': return fLeft / fRight;
            case '^': return pow(fLeft,fRight);
            case '_': return pow(fLeft,1.0f/fRight); 
            case '%': return fmod(fLeft,fRight);

            //case '?': return bSelect = ?;
            //case ':': return (bSelect) ? fLeft : fRight;

            //case '>': return fLeft > fRight;
            //case '<': return fLeft < fRight;
            //case '>=': return fLeft >= fRight;
            //case '<=': return fLeft <= fRight;
            //case '==': return fLeft == fRight;
            //case '!=': return fLeft != fRight;
            //case '||': return fLeft || fRight;
            //case '&&': return fLeft && fRight;

            case '&': return static_cast<number_t>(static_cast<unsigned long>(fLeft) & static_cast<unsigned long>(fRight));
            case '|': return static_cast<number_t>(static_cast<unsigned long>(fLeft) | static_cast<unsigned long>(fRight));
            case '~': return static_cast<number_t>(~static_cast<unsigned long>(fRight));
            case '>>': return static_cast<number_t>(static_cast<unsigned long>(fLeft) >> static_cast<unsigned long>(fRight));
            case '<<': return static_cast<number_t>(static_cast<unsigned long>(fLeft) << static_cast<unsigned long>(fRight));

            default:  
                {
                    printf("ERROR: Invalid Operator Found\n");
                    return 0.0f;
                }
        }
    }
    else if(nType == TOKEN_NUMBER)
        return pNode->fValue;
    else if(nType == TOKEN_CALL)
        return CreateCall(pNode); //not implemented
    else if(nType == TOKEN_GLOBAL)
        return GetGlobal(pNode);
    else if(nType == TOKEN_ARGUMENT)
        return GetArgument(pNode);
    else if(nType == TOKEN_STRING)
        return 0.0f;

    return 0.0f;
}

Какие-либо советы / указатели / советы или полезные ссылки о том, как мне это сделать?


Небольшой набор примеров (по запросу):

Что у меня уже работает

Ввод: 2 * (3 ^ 1.5) - 4 / (1 << 3)

Вывод: In-Order: 2.0 * 3.0 ^ 1.5 - 4.0 / 1.0 << 3.0

Pre-Order: - * 2.0 ^ 3.0 1.5 / 4.0 << 1.0 3.0

Post-Order: 2.0 3.0 1.5 ^ * 4.0 1.0 3.0 << / -

Result: 9.892304

Что я хочу добавить

Ввод: (GetDay() == 31) ? -15.5 : 8.4

Выход: 8.4

Выход на 31-е: -15.5

Ввод: max([0],20) (где [0] обозначает аргумент 0, а [0] = 35)

Выход: 20

Ввод: (GetField('employees','years_of_service',[0]) >= 10) ? 0.15 : 0.07 (где [0] - аргумент 0, а [0] - допустимый индекс)

Вывод (если year_of_service для emplyee меньше 10: 0.15

else Вывод: 0.07

Это в основном математика с некоторыми дополнениями, вдохновленными C, за исключением того, что аргументы передаются не по имени, а по индексу, а строки экранируются одинарными кавычками, а не удваиваются.

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

Ответы [ 2 ]

1 голос
/ 19 июля 2010

Я думаю, вы должны изменить структуру "Node", чтобы иметь массив дочерних элементов, вместо "pLeft" и "pRight".Функция типа sin () имеет один аргумент / child.Условный (троичный) оператор имеет три аргумента / потомка.

1 голос
/ 19 июля 2010

Что нужно сделать?и: зависит от дерева, созданного парсером.Я буду притворяться, что парсер генерирует дерево типа

      ?
  b       :
        t   f

Сначала вам не нужно оценивать деревья до переключения, и в большинстве мест вы меняете что-то вроде

fLeft + fRight;

на

EvaluateTree(pNode->pLeft) + EvaluateTree(pNode->pRight);

С заменой + всеми различными операторами.

Для?: Вы делаете ....

case ':': return 0.0f; /* this is an error in the parse tree */
case '?': if (!(pNode && pNode->pLeft && pNode->pRight &&
                pNode->pRight->pLeft && pNode->pRight->pRight))
             /* another error in the parse tree */
             return 0.0f;
          return EvaluateBool(pNode->pLeft) ?
                   EvaluateTree(pNode->pRight->pLeft) :
                   EvaluateTree(pNode->pRight->pRight) ;

Для определения EvaluateBool у вас есть пара вариантов.Путь C более или менее

BOOL EvaluateBool(Node* pNode)
{
    return (EvaluateTree(pNode) == 0.0) ? FALSE : TRUE;
}

Затем вам нужны определения для «<» и друзей, которые возвращают 0,0 для «ложь», и все остальное для «истина».Значение -1 является очень популярным истинным значением, хотя обычно для хранения bool в целых числах. </p>

Более структурированный способ - переместить все операторы типа '<', которые возвращают логические значения в тело EvaluateBool, и сделатьон работает более или менее так же, как EvaluateTree. </p>

Наконец, вместо создания троичного оператора?: использовать два узла, вы также можете изменить определение узла (и анализатора), чтобы иметь до трехвложенные деревья, то у большинства операторов будет два дерева, но?: будет три.Может быть, что-то вроде

case '?': return EvaluateBool(pNode->pLeft) ?
                   EvaluateTree(pNode->pMiddle) : 
                   EvaluateTree(pNode->pRight) ;

Но тогда вам придется переписать обходы дерева заказов до и после заказа.

Вторая часть, функции.Один из способов сделать это - сохранить имя функции в szValue.Еще есть несколько разных значений для nType в зависимости от функции.Вам нужно будет выбрать какое-то правило в парсере и использовать его здесь в интерпретаторе.Вы можете сделать что-то вроде ...

else if(nType == TOKEN_CALL)
    return EvaluateFunc(pNode);

Тогда EvaluateFunc может выглядеть примерно так:

number_t EvaluateFunc(Node* pNode)
{
    if ((pNode == NULL) || (pNode->szValue == NULL))
        return 0.0f;
    if (0 == strcmp('cos', pNode->szValue))
        return my_cos(EvaluateTree(pNode->pLeft));
    else if (0 == strcmp('gcd', pNode->szValue))
        return my_gcd(EvaluateTree(pNode->pLeft),
                      EvaluateTree(pNode->pRight));
    /* etc */
    else /* unknown function */ return 0.0f;
}

Выглядит как забавный проект, наслаждайтесь!

...