如何构建谓词/表达式/规则/动作引擎

时间:2013-10-05 18:38:43

标签: javascript php expression predicate

好的,首先,对不起TL; DR ......

第二,关于我想要达到的目标的一些背景。

我有一个处理给定文件的脚本,并根据一组定义的规则生成一个新文件。文件中的每一行都有一个值,脚本会评估该值,并根据该值和规则生成其他值。生成的文件包含原始值和csv格式的生成值。

例如:

输入文件:

row1value
row2value

输出文件:

"row1value","generatedValue1","generatedValue2","generatedValue3"
"row2value","generatedValue1","generatedValue2","generatedValue3"

例如,假设源文件在每一行上都有冒号分隔值,目标是用冒号分割值并将每个子值放入列中。这就是"之前" """"看起来像是:

输入文件:

a:b:c
some:random:value

输出文件:

"a:b:c","a","b","c"
"some:random:value","some","random","value"

因此要生成行,我可能会:

function generateRow ($key) {
  // $key is the value for the current row being processed from the source file
  // $row is the array that will contain the columns to be inserted to the output file
  $row = array();

  // generate the column values
  $row = explode(":",$key);

  // make the original value ($key) the first column
  array_unshift($row, $key);

  // return the array. some other function will fputcsv it
  return $row;
}

另一个例子是源文件包含如下行的值:

[prefix]:[url]

输出文件中列的值为:

  • column1 =完整的原始值(它始终是这个)
  • column2 = [prefix]
  • column3 = [url]
  • column4 =解析网址
  • 的[url]
  • column5 =解析文件扩展名
  • 的[url]

所以我写generateRow()来做这些事情。

好的,所以我拥有这一切。这一切都很闪亮,工作得很好。我要求制作新的"流程"接收自己的文件并根据定义的规则生成包含列的新文件。我只想将所有这些作为背景故事,将其置于上下文中,以便所有人都能更容易地理解我真正想要的东西。

所以"问题"是这样的,"过程"是由编码员(我)根据规范写出generateRow()。对于我自己或任何知道php的人来说,这并不是特别困难。但这不是用户友好的#34;因为某些利益相关者希望自己负责创建这些东西,但他们不是编码员。

所以我现在的任务是基本上为generateRow()提供一个网络界面。所以我看到它的方式,我基本上需要创建一个表单,动态生成表单元素和基于以前选择的项目的可能选项。换句话说,表达引擎。或谓词引擎。或者规则构建器。老实说,我并不是百分之百确定这些术语中哪些是最准确的,但我已经做了大量的谷歌搜索和阅读,到目前为止这些都是我提出来的用。

因此,例如,表单将首先要求用户创建条件或赋值表达式,

[下拉:如果|设为]

IF:如果用户点击"如果"然后显示另一个下拉列表,其中列出了"变量"检查。例如,系统将可用作选择对其他列的引用,源文件中的任何列(包括键),导入文件名或用户先前创建的任何自定义变量(请参阅" set&#34 ;下面)。然后表单将显示" action"下拉列表,将显示"设置","未设置","大于","正则表达式"等。如果用户选择类似"则设置"或"未设置"然后没有其他字段输出。但是,如果用户选择"大于",则会显示另一个下拉列表,要求从"变量"列表中选择,或输入一个输入字段。例子:

IF column1 "is set"           /* check if column1 is set */
IF column1 contains "foobar"  /* check if column1 contains "foobar" */
If column1 regex "^[a-z]+$"   /* check if column1 contains only letters */

一旦定义了这个,用户就可以添加" set"里面的表情。为简单起见,我不认为我需要嵌套条件或使用AND | OR来制作复合条件。

SET:因此,如果用户选择此选项,表单将指导他们构建赋值表达式。第一个下拉列表将保存用户可以为其赋值的变量,例如输出文件的列或临时变量,以便可以在其他表达式中引用它们。例子:

SET [column1] [=] ["foobar"]
SET [column2] [=] [column1]
SET [column1] [regex] [userVar1] ["^[^:]+"]
SET [userVar1] [explode] [key] [":"] 

嗯,我不确定这是最好的"提出它的方式,但希望你能得到这个想法。

然后我会保存这些表达式,然后编写PHP代码来评估它们;基本上将它们转换为实际的PHP代码。我认为我实际上对那部分很好:我只会使用解释器模式。

但我需要帮助的是整个表达式"映射" /"建设者"部分。实际上,我认为我甚至可以摆动"建设者"部分,如果我能得到一个"映射"充实的可能表达方式。

所以,是的,我的问题的核心在于:如何绘制可能的表达方式。目前我一直在尝试将其构建/映射为xml,但我似乎无法掌握如何做到这一点,只是硬编码每一条可能的路径。首先,对我而言,这似乎有点低效,就像应该有一个更聪明的方法来做到这一点。它可以很容易地扩展......让我们说"过程" #1的规则集有2个要绘制的源文件列和5个输出文件列来生成..和" process" #2是1还是4?如何计算用户可以设置的可变数量的用户定义变量。

那么,有没有人有任何提示或链接到tuts解释如何做这种事情?或者更好的是预制(php)解决方案会很好,但我还没能找到一些东西..

编辑:以下是我现在所处位置的示例,希望能更好地了解我的问题。

例如,如果我只是对地图进行硬编码(xml),它会如何排序?

<expressions>
  <expression type='if'>
    <variable name='column1'>
      <operator type='isset'></operator>
      <operator type='notset'></operator> 
      <operator type='equals'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
      <operator type='greaterThan'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
      <operator type='lessThan'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
    </variable>
    <variable name='column2'>
      <operator type='equals'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
      <operator type='greaterThan'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
      <operator type='lessThan'>
        <variable name='column1' />
        <variable name='column2' />
        <variable name='column3' />
      </operator>
    </variable>
</expressions>

情侣笔记:

  1. 有任意数量的variable个节点。每个源文件列将有一个,每个输出文件列名称一个,用户在运行中创建的每个变量一个,等等。
  2. 存在可变数量的operator类型。我刚刚展示了3个例子,但到目前为止,我已经想到了20个,我已经想到了
  3. 请注意,某些operator类型需要在表单上输出其他表单字段。例如,&#34; if [column1] [isset]&#34; vs.&#34; if [column1] [greaterThan] [column2]&#34;
  4. expression节点显示&#34; if&#34;表达式看起来像。还会有一个类型为&#34; set&#34;的expression节点。这将具有不同的结构,但原则上工作相同。例如,可能有一个表达式&#34; [set] [column2] [equals] [column1]&#34;或&#34; [set] [column1] [regex] [sourceColumn1] [输入值(正则表达式)]&#34;
  5. 所以现在我可以沿着这条路走下去,根据树下的可用内容动态构建菜单。

    但正如你所看到的,似乎有很多重复,并且将新东西投入混合物(如新操作员)也是一个小问题。所以这里的问题是,我如何更好地构建它?或者它是不可能的,唯一的解决方案是硬编码每种可能性?

3 个答案:

答案 0 :(得分:3)

哇,这是一个很好的问题!我将从您的直接问题的答案开始,然后触及一般问题。

如果你想要现成的产品,有&#34;产品配置&#34;诸如3dfacto或可能Drools之类的系统采用一组规则(在这种情况下,是关于如何构建系统规则的规则),并且可以创建仅允许输入有效规则的动态表单。它实际上与您上面写的XML没有太大的不同。我们来看看发生了什么:

首先,您要定义一种语言来表达系统中的规则。您的语言结构称为语法,我们通常使用BNF来描述语法。例如:

command := <if> | <set>
if := "if" <boolexpr> "then" <command>
boolexpr := <column> isset |
            <column> notset | 
            <column> "greaterThan" <column> |
            <column> "lessThan" <column>
<column> := "column1" | "column2" | ... | "column_n"
set := <column> <equals> <value> | ...
value := <column> | <number>

请注意,语法的某些部分可能需要涉及代码。例如,&#34;列&#34;部分是动态的,取决于实际列的数量。您可以为{Drools规则引擎take a look at the grammar添加Processing,尽管有很多代码混合在其中。

语法的BNF表示与XML映射之间的关键区别是您可以使用的:前向和后向链接。这两者代表完全相同的东西,但是BNF比XML更紧凑。这是因为可以引用子结构而不是按原样复制子结构。例如,我们可以使用<value>规则,只要我们不关心它是列还是字面数字。另一种思考方式是,规则中的路径比规则要多得多。

我们如何在代码中表示语法?一种方法是使用数组。

$column = a_function
$boolexpr = Array("or", Array("match", $column, "isset"), Array("match", $column, "notset"), Array("match", $column, "greatherthan", $column), Array("match", $column, "lessthan", $column))
$command_grammar = Array("match", "If", $boolexpr, "then", $command)

您的表单构建器系统将基本上对此语法进行抽象解释。你问:&#34;给出输入到这一点,有效的下一个输入是什么?&#34;而你通过解释目前为止输入的规则的语法,并看到接下来会发生什么来做到这一点。你提到了&#34;翻译&#34;模式,所以我认为你熟悉这个。

另一种思考方式是你正在制作一个解析器。通常,解析器尝试通过语法规则尝试匹配输入的每个可能路径,并尽快拒绝任何路径:

  1. 到目前为止,输入和路径不同
  2. 路径已经完成,但还有更多输入。
  3. 输入已完成,但路径上还有非空语法规则。
  4. 在您的系统中,(1)和(2)永远不会发生,因为您不允许输入无效规则。 (3)可能因为用户没有完全指定规则而发生,因此您可以查看下一步更新表单的语法规则。

    让我们退后一步,看看我们正在尝试做什么:允许非程序员对计算机进行编程。一个常见的问题是,使这些系统变得灵活的尝试导致它们成为他们自己的编程语言。通常它们比现有语言更糟糕,然后你需要成为程序员才能使用它。我认为你有一些正确的想法,比如说&#34;或&#34;会太复杂了。限制复杂性可以避免使用这种图腾。

    避免此问题的另一种方法是利用您现有的语言,如PHP。您可以定义使任务更容易的函数,并让最终用户使用您的示例作为指南直接编写PHP。持续反馈有助于此。请查看{{3}}以获取此方法的示例。

答案 1 :(得分:1)

好吧,正如我所知,你想要一个规则生成系统,所以用户发送主字符串或文件并说出他想要的东西,然后规则生成器根据用户需要制定规则,然后将它们作为规则发送到generateRow() ,然后generateRow()返回值;

1-正如你所说,你必须将主要的操作规则(如equal,GT,LT或contains,regExp等)定义为可能的数组,并在键入时将它们分组,例如regExp或包含在字符串中使用,所以我们这里有两组操作规则,字符串,数字。然后使用此组和注释创建一个xml文件。为每个操作员做一个笔记,如:

<strings>
 <oprator>
  <title>equal</title>
  <php_func>stristr</php_func>
  <input_arg_count>2</input_arg_count>
  <true_return>true</true_return>
 </oprator>

 <oprator>
  <title>not_in</title>
  <php_func>stristr</php_func>
  <input_arg_count>2</input_arg_count>
  <true_return>false</true_return>
 </oprator>

</string>

3-你必须询问用户输入文件,桩或列的类型是什么类型,如果基于col,列分隔符是什么类型,例如,或者或者:

2-在用户界面中,你必须询问将要运行的列类型。例如col1 type:string。 col2 type:int,如果用户输入基于cols。否则,如果用户输入文件只是桩文件,则规则生成器必须设置为字符串。否则必须按用户设置数据设置为特殊规则组,例如,如果col1是字符串,则将可能的运算符设置为字符串运算符,显示用户字符串运算符。

3-一些运算符必须有2个输入参数(input_arg_count),所以你必须划分有关列的问题,这些列必须在此操作中运行

4- u必须使用输入收集每个用户规则(更好地使用字符串)将其作为操作字符串发送到规则生成器。

5-规则生成器用除法运算字符串(由你定义的分频器),然后在开关中,制定规则。

6-开关:在这个开关中必须检查用户输入的文件类型,如果是桩,调用你的桩类,如果是基于col的基于col的类,甚至你可以通过第一个操作符,axample,使桩文件成为基于col的文件如果第一个运营商有|所以,你可以通过|来分配文件,那么你有一个基于col的文件。这将重新使用操作

然后检查操作tyle,然后在posible oprators xml文件中,你可以理解操作的类型。并创建一个main函数,将oprators作为数组,另一个参数获取运算符,并作为最后一个参数从posible运算符xml文件中获取可用的php函数。所以系统现在将使用哪个函数,哪个oprator和参数。

8-返回第一次操作的恢复后。发送到下一个操作,如循环函数,所以操作将是operat。

你可以把它放到git hub中,所以我可以帮助你开发它。

祝你好运

答案 2 :(得分:0)

如果在您的示例中原始值始终是字符串,并且规则始终对该字符串进行一些修改,以便生成的值始终只能从字符串中扣除,您是否考虑过机器学习方法?您可以让用户填写原始值和生成值的示例,您的算法可以尝试从示例中推断出规则。

This paper似乎正在做你想要的事情(但可能稍微复杂一些;如果你的所有规则基本上都是正则规则,那可能会更容易)。

这个选项应该是可扩展的,如果你能找到合适的算法,它应该尽量减少你做繁忙工作的需要,但它并不完美,如果你采用这种方法,可能会有一个用户在某个时候有一些非常复杂的规则需要手写。