【问题标题】:Adding Conditionals & Functions to a Math Parser向数学解析器添加条件和函数
【发布时间】:2010-07-16 07:46:57
【问题描述】:

我有一个基于二叉树的数学表达式解析器,它非常适用于“普通”数学,例如:(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 &lt;&lt; 3)

输出:In-Order: 2.0 * 3.0 ^ 1.5 - 4.0 / 1.0 &lt;&lt; 3.0

Pre-Order: - * 2.0 ^ 3.0 1.5 / 4.0 &lt;&lt; 1.0 3.0

Post-Order: 2.0 3.0 1.5 ^ * 4.0 1.0 3.0 &lt;&lt; / -

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]) &gt;= 10) ? 0.15 : 0.07(其中 [0] 是参数 0,[0] 设置为有效索引)

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

其他输出:0.07

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

完成最后一点后,我希望编译字节码或 JIT输入集可以更改,但它被频繁使用,因此它需要“快速”,并且需要非程序员也可以使用。

【问题讨论】:

  • 我知道你想避免它,但是解析器生成器的存在让你可以专注于真正重要的事情:你的语法。然而,我承认为教育目的创建一个很有趣。
  • 是否有可能提供一个完整的示例,包括一些输入数据以及您期望的输出是什么?
  • @ereOn:是的,这比其他任何事情都更有助于理解和启发@Jon Cage:当然,我现在会添加一些示例:)
  • 您是在问如何解析 i 表达式或如何表示和评估它?作为旁注,EvaluateTree 不应该是 Node.js 的成员函数。此外,您可能需要考虑为不同的节点类型使用派生类,并摆脱节点类型字段和 switch 语句。
  • hmmm,从来没有这样想过,但我想说更多的是如何表示和评估表示。至于为什么我不使用类(还),我决定让这件事以最简单的形式实际工作,然后我才完全喜欢 OOP。

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


【解决方案1】:

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

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

然后你需要定义 '

更结构化的方法是将所有返回布尔值的运算符(如“

最后,除了使三元运算符 ?: 使用两个节点之外,您还可以将节点(和解析器)的定义更改为最多具有三个子树,然后大多数运算符将具有两个树,但是?:会有三个。也许像

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

看起来很有趣的项目,享受吧!

【讨论】:

  • 我喜欢实现三元运算符的想法,但是,你的帖子也给了我另一个想法,那就是创建一些不同的树类型,布尔,选择,代数和函数,所以我可以拥有更好的控制和更多的权力,因为就目前而言,我发现我当前的表达式树存在问题,其中的函数需要超过 2 个参数,以及具有多个条件的布尔语句。我要试试这个:)
  • 再往前走一点,你就进入了面向对象设计的领域。每种运算符都是不同类型的树节点,大多数有两个子节点,但都处理评估、预排序、后排序、中序类型方法。没有规则反对在 C 甚至 Fortran 中执行 OO。享受吧!
  • 好吧,我的灵感并没有走得太远,但是,我使用 left|middle|right 节点的想法非常适合函数解析(使用每个中间节点为每个节点保存一个表达式树arg),同样适用于布尔求值。刚刚做了一些最后的调整,然后以一种很好的面向对象的方式进行调整:)
【解决方案2】:

我认为您应该将“Node”结构更改为具有子数组,而不是“pLeft”和“pRight”。像 sin() 这样的函数有一个参数/子项。条件(三元)运算符具有三个参数/子项。

【讨论】:

    猜你喜欢
    • 2014-12-11
    • 1970-01-01
    • 2020-06-07
    • 1970-01-01
    • 1970-01-01
    • 2019-07-05
    • 2020-09-14
    • 2011-04-25
    • 2011-11-21
    相关资源
    最近更新 更多