添加条件和&数学解析器的功能

时间:2010-07-16 07:46:57

标签: c++ c expression-trees mathematical-expressions

我有一个基于二叉树的数学表达式解析器,它可以很好地用于“普通”数学,例如:(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]设置为有效索引)

输出(如果emplyee的years_of_service小于10:0.15

其他输出:0.07

它基本上是带有一些C启发附加的数学,除了参数不是通过名称传递,而是通过索引传递,并且字符串由单引号转义而不是双引号。

当我完成最后一位时,我希望无论是字节码编译还是JIT它,因为我计划将它用于游戏或数学依赖程序,其中输入集数据是常量,但是输入集可以改变,但它经常使用,所以它需要“快速”,并且需要非程序员使用。

2 个答案:

答案 0 :(得分:1)

正确的做法是什么?和:取决于解析器生成的树。我会假装解析器生成一个像

这样的树
      ?
  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;
}

然后你需要'&lt;'的定义以及为false而返回0.0的朋友,以及其他任何为true的朋友。值-1是一个非常流行的真值,但通常用于以整数存储bool。

更有条理的方法是移动所有运算符,例如'&lt;'将布尔值返回到EvaluateBool的主体中,并使其像EvaluateTree一样工作。

最后,不是使三元运算符?:使用两个节点,你也可以改变节点(和解析器)的定义,最多有三个子树,然后大多数运算符会有两棵树,但是?:会有三个。也许像是

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;
}

看起来像一个有趣的项目,享受!

答案 1 :(得分:1)

我认为你应该改变你的“Node”结构来拥有一个子数组,而不是“pLeft”和“pRight”。像sin()这样的函数有一个参数/ child。条件(三元)运算符有三个参数/子元素。