如何在自定义表达式求值程序中检测循环逻辑或递归?

时间:2008-11-26 20:46:48

标签: recursion evaluation

我编写了一个实验函数赋值器,它允许我将简单函数绑定在一起,这样当变量发生变化时,所有依赖于这些变量的函数(以及依赖这些函数的函数等)都会同时更新。我这样做的方式是不是在输入时立即评估函数,而是存储函数。只有当我要求输出值来评估函数时,我才会在每次请求输出值时对其进行评估。

例如:

pi = 3.14159
rad = 5
area = pi * rad * rad
perim = 2 * pi * rad

我将'pi'和'rad'定义为变量(以及返回常量的函数),并将'area'和'perim'定义为函数。无论何时'pi'或'rad'发生变化,我都希望'area'和'perim'的结果会有所改变。同样,如果有任何函数取决于'area'或'perim',那么这些函数的结果也会改变。

这一切都按预期工作。这里的问题是当用户引入递归时 - 无论是偶然的还是有意的。我的语法中没有逻辑 - 它只是一个评估者 - 所以我无法为用户提供一种“突破”递归的方法。我想防止它发生,这意味着我需要一种方法来检测它并将有问题的输入声明为无效。

例如:

a = b
b = c
c = a

现在评估最后一行导致StackOverflowException(前两行评估为'0' - 未声明的变量/函数等于0)。我想做的是检测循环逻辑情况并禁止用户输入这样的语句。无论循环逻辑隐藏得多深,我都想这样做,但我不知道如何去做。

在幕后,顺便说一下,输入字符串通过简单的扫描仪转换为标记,然后通过手写的递归下降解析器转换为抽象语法树,然后评估AST。语言是C#,但我不是在寻找代码解决方案 - 仅逻辑就可以了。

注意:这是我用来了解解析器和编译器如何工作的个人项目,因此它不是关键任务 - 但是我从中获取的知识确实计划在某些时候投入现实生活中。你们可以提供的任何帮助都将受到极大的赞赏。 =)

编辑:如果有人好奇,this post on my blog描述了我为什么要学习这个,以及我从中得到的东西。

3 个答案:

答案 0 :(得分:6)

过去我遇到过类似的问题。 我的解决方案是将变量名称推送到堆栈,因为我通过表达式递归来检查语法,并在退出递归级别时弹出它们。

在我将每个变量名称推送到堆栈之前,我会检查它是否已经存在。 如果是,那么这是一个循环引用。 我甚至能够在循环引用链中显示变量的名称(因为它们将在堆栈中,并且可以按顺序弹出,直到我到达有问题的名称)。

编辑:当然,这是针对单个公式...对于您的问题,变量赋值的循环图将是更好的方法。

答案 1 :(得分:2)

解决方案(可能不是最好的)是创建依赖关系图。

每次添加或更改函数时,都会检查依赖关系图的圆柱体。

这可以缩短。每次添加或更改功能时,都会标记它。如果评估结果是对标记的函数的调用,则您有一个循环。

示例:

  

a = b

  • 标记一个
  • eval b(未找到)
  • unflag a
  

b = c

  • flag b
  • eval c(未找到)
  • unflag b
  

c = a

  • flag c
  • 评估
  • eval b
  • eval c(已标记) - >循环,丢弃更改为c!
  • unflag c

答案 2 :(得分:0)

回复对答案二的评论:

(对不起,刚搞砸了我的openid创作,所以我将不得不把旧的东西链接起来......)

如果你为“pop”切换“flag”和“unflag”,那几乎是一样的:) 使用堆栈的唯一优势是,无论深度如何,您都可以轻松地提供有关循环的详细信息。 (对于错误消息很有用:))

安德鲁