基于反射分析表达式



我需要执行函数来计算表达式,类似于Excel,但使用PHP(OOP)。(对于任何有语言解析器知识的人来说,这个问题可能都是个花生。)

核心引擎分析功能及其需求(反映),并应运行适当的功能,并按最佳顺序运行,以便:

  • 解析未知值
  • 设置新值时,更新任何从属值

场景

a = b * c
b = c + d
c = 5
d = 8
e = f * 2
f = b + 3 + a

获取任何计算值

  • 解决a需要解决b
  • 解决e需要解决f(这反过来又需要解决
    a和
    b)

设置已知值

如果更改了c,则应运行函数来重新计算值。

  • [a=b*c]应该保留,因为它将基于废弃的b值
  • [b=c+d]应该运行,因为不存在未知的依赖项
  • 任何依赖于b的函数现在也应该运行,这样[a=b*c]就可以运行了
  • 通过更新,现在应该运行函数[f=b+3+a]
  • 同样,函数e现在也应该运行

问题

我的问题是关于用于解析和设置值的算法

  • 尽管它似乎是递归解析的主题,但它是否更明智地迭代一系列函数并将任何未解决的需求放在最后

例如:[Update C]不应按源顺序[a=..]、[b=..]运行,而应按[b=]、[a=]运行。

  • 在解析值时是否应该已经使用SET逻辑?如果是,那么查找上面的值的解析是否会变得多余?类初始化后,将设置常量并运行相关函数

递归执行示例:

[Update C] =>
[Update B] =>
[Update A] =>
[Update F] =>
[Update E] =>

再一次,这个执行顺序是否应该通过在平面数组上迭代来调用recursivly或类似于上面的命令。

感谢你在这件事上的任何参考或原则。

这不是OO代码,更多的是概念验证(我很无聊)。

代码基本上分为两半。首先是有效地标记字符串的代码,然后为每个变量创建依赖项列表。所以a取决于b和c等。

第二部分是一个蛮力系统,它遍历每个依赖项列表,检查所有依赖项是否都是可解的,每次它都能解决它(所有依赖字段都是已知的),它会在变量列表中添加一个序列,说明它们可以按什么顺序运行。它会一直这样做,直到所有依赖项都是可求解的,或者它没有取得更多进展(可能类似于a=b和b=a)。

我在代码中添加了注释,可能会有所帮助。。。

$equations = [
"a = b * c",
"b = c + d",
"c = 5",
"d = 8",
"e = f * 2",
"f = b + 3 + a",
];
$variables = [];
$toBeSolved = [];
$symbols = ["=", "*", "+", "-", "/"];
$evalSeq = 0;
// Extract dependencies and known values
foreach ( $equations as $equation ) {
$parts = explode ( " ", $equation);
$equationVars = [];
$leftVar = array_shift($parts);
$variables[$leftVar] = null;
foreach ( $parts as $part ) {
if ( !in_array ($part, $symbols) && !is_numeric($part) )  {
$variables[$part] = null;
$equationVars[] = $part;
}
}
// If there are dependant values, add to list of things to be solved
if ( count($equationVars) > 0 ) {
$toBeSolved[$leftVar] = $equationVars;
}
// No dependants, so just log sequence as solved
else    {
$variables[$leftVar] = $evalSeq++;
}
}
// Find equations solvable, carry on whilst unknowns left
while ( count($toBeSolved) > 0 ){
$progress = 0;
foreach ( $toBeSolved as $var => $dep )   {
$known = true;
foreach ( $dep as $variable )   {
// Flag an unknown variable, so can't solve this yet
if ( $variables[$variable] === null )    {
$known = false;
break;
}
}
// If all values are known, then can be evaluated, so log sequence and remove
if ( $known )   {
$variables[$var] = $evalSeq++;
unset ($toBeSolved[$var]);
$progress++;
}
}
// If no progress has been made, then can't reduce further.
if ( $progress == 0 ){
break;
}
}
print_r($variables);
// If $toBeSolved is not empty, then can't solve
print_r($toBeSolved);

最终的结果是。。。

Array
(
[a] => 3
[b] => 2
[c] => 0
[d] => 1
[e] => 5
[f] => 4
)

表明在中求值的顺序是c、d、b、a、f、e。

可能的情况是,您可以存储值,而不是存储序列,这样在每次传递时,您就可以计算方程的结果,并将其与变量名一起存储。

看起来这个问题由两个单独的问题组成。目标还不太明确,但我会尽力回答。

第一个是您的语法允许"正向引用"。这个代码就是一个确切的例子:

e = f * 2
f = b + 3 + a

变量声明e引用了稍后在其主体中声明的实体(f),因此您肯定无法在源代码的一次解析过程中解析它。最接近的行为可以在JavaScript语言中找到,其中吊装以类似的方式工作。据我所知,要实现运行时遍历此代码两次。首先,它收集当前范围中存在的所有声明。在第二次传递过程中,它可以找到一个相应的声明节点,由执行的函数引用。当然,这过于简单化了,但我希望总体思路是清晰的。

第二个问题是没有定义执行顺序。或者可以说,它是由声明函数的依赖项隐式定义的。因此,如果你想根据这些依赖关系计算执行顺序,你可以实现一个规范的拓扑排序算法。您应该小心循环引用,因为在这种情况下,此算法将无法找到正确的序列。

此外,如果你的语言的语法比简单的算术表达式更复杂(例如,即使在这种情况下,你也应该考虑运算符优先级和关联性),最好找一些符号计算库,也许这会大大简化你的任务。

最新更新