【问题标题】:How to generate random programs from BNF如何从 BNF 生成随机程序
【发布时间】:2018-10-06 17:56:09
【问题描述】:

我知道我的问题听起来有点含糊,但我在网上找不到任何教程。我不是要答案,而是要更多的解释。 BNF 的一个例子:

<prog> ::= “int main() { <stat_list> return 0; }”
<stat_list>  ::= <stat>
         | <stat_list> <stat>
<stat>       ::= <cmpd_stat>
         | <if_stat>
         | <iter_stat>
         | <assgn_stat>
         | <decl_stat>
<cmpd_stat>  ::= { <stat_list> }
<if_stat>    ::= if ( <exp> ) <stat>
         | if ( <exp> ) <cmpd_stat>
         | if ( <exp> ) <stat> else <stat>
         | if ( <exp> ) <cmpd_stat> else <stat>
         | if ( <exp> ) <stat> else <cmpd_stat>
         | if ( <exp> ) <cmpd_stat> else <cmpd_stat>

将其转换为 python 以让我的程序使用上述条件创建一个随机程序的最简单方法是什么?任何有用网站链接的帮助将不胜感激。

【问题讨论】:

    标签: python parsing code-generation bnf


    【解决方案1】:

    NLTK 有一个用于grammars 的包。通常用于句子分析,但没有什么能阻止您使用它来创建遵循该规则的“程序”。

    我认为 NLTK 只允许你定义上下文无关语法,所以我在这里给你留下一个我做过的小例子:

    from nltk import CFG
    from nltk.parse.generate import generate
    
    #Define your grammar from string
    #You can define it using other methods, but I only know this xD
    
    grammar = CFG.fromstring(""" S -> NP VP
      VP -> V NP
      V -> "mata" | "roba"
      NP -> Det N | NP NP
      Det -> "un" | "el" | "con" | "a" | "una"
      N -> "bebé" | "ladrón" | "Obama" | "perrete" | "navastola" | "navaja" | "pistola" """)
    
    ''' This grammar creates sentences like:
            El bebé roba a Obama
            Baby steals Obama (in spanish)
    '''
    #With this we "create" all the possible combinations
    grammar.productions()
    
    #Here you can see all the productions (sentences) with 5 words
    #created with this grammar
    for production in generate(grammar, depth=5):
        print(' '.join(production))
    

    【讨论】:

      【解决方案2】:

      您可以通过滥用解析器将其变成生成器来做到这一点。

      首先,为您的语言构建一个递归解析器。 (See my SO answer on how to do just that)。 暂停当你读到那个......我现在假设你明白如何做到这一点。

      您会注意到,这样的解析器充满了从解析器函数对一个语法规则的调用,以及对其他语法规则或原始标记匹配器的其他函数的调用。

      您想要做的是修改每个调用,以决定在调用之前如果函数中仍有一些替代方法可用,它将以较低的概率返回“true”。如果调用决定为 false,则控制权简单地传递给解析器的另一部分。如果呼叫决定为真,它实际上会拨打电话;被调用者现在必须以返回 true 并生成相应源代码的方式行事。在某些时候,这将强制对令牌读取器的调用返回 true;令牌阅读器被一个发出随机令牌的打印函数所取代。当你这样做时,实际发生的事情是,现在决定某事是否为真的调用只是成为调用;我们不再需要返回状态,因为被调用的函数必须返回 true。这将我们的functions-returning-bools 更改为procedures-returning-void。请参阅下面的示例..

      让我们尝试一个简单的编程语言的简单语法示例p

      p = s ;
      s = v '=' e ;
      s = 'if' e 'then' s ;
      e = v ;
      e = v '+' n ;
      

      好的,我们的递归下降解析器 p(我不是 Python 人,所以这是伪代码):

      function p() { return s(); } // no alternatives
      function s() { if v()
                     then if match("=")
                          then return e()
                          else return false;
                     else if match("if")
                          then if e()
                               then if match("then")
                                    then return s()
                                    else return false;
                               else return false;
                          else return false;
                    }
       function e() { if v()
                      then if match ("+")
                           then if n()
                           else return true
                      else return false
                    }
       function v() { return match_variable_name(); }
       function n() { return match_integer_constant(); }
      

      好的,现在让我们使用随机返回 true 或 false 的硬币翻转函数来强制调用来决定它们是否会成功。任何形式的构造:

                if <testsomething> then <action x> else <action y>
      

      变成:

                if flip() then  { <testsomething> <action x> } else <action y>
      

      以及任何形式的构造:

                if  <testsomething> then <action x> else return false
      

      变成了

                { <testsomething>; <action x> }
      

      因为如果我们要生成可解析的程序,它必须成功。

      如果 testsomething 是对另一个语法规则的函数调用,我们不理会它。对原始标记匹配的函数调用变成了打印语句:如果 testsomething 是“match(Q)”, 然后用“print(Q)”替换它;这就是实际生成程序的一部分。

      procedure p() { s(); } // no choice, this has to succeed
      procedure s() { if flip() // flip == true --> v must succeed
                     then { v();
                            print("=") // because if no match, procedure fails
                            e();
                          }
                     else { print("if")  // if we get here, must succeed
                            e();
                            print("then"); // because match("then") must succeed
                            s();
                          }
                    }
       procedure e() { v(); // because there are no alternatives
                       if flip() then { print("+");
                                        n();
                                      }
                       else { }
                     }
       procedure v() { print_variable_name(); }
       procedure n() { print_integer_constant(); }
      

      请注意,变量名称和整数常量的标记识别器现在变成了打印随机变量名称/常量的打印过程。这本质上也只是将“翻转”推到这些程序中。

      现在这可能会打印任意长的程序,因为翻转可能会迫使 s 重复调用自身。如果翻转是 50-50,那么您在 1000 中 1 中进行 10 次递归的机会可能还可以。但是,您可能会决定根据目前生成的输出大小或任何递归的深度,让每个单独的翻转选择较短的短语。

      现在,这在一般情况下不会产生语义正确的程序。那是因为我们的解析器是“上下文无关的”;对其他部分强制生成的代码的一部分没有限制。例如,如果您的语言必须在使用变量之前声明它,则此方案不能保证在 randome-var-X 出现在表达式中之前会生成 random-var-X 的声明。

      没有简单的方法可以解决这个问题,因为语言语义不能保证“简单”。只是表明解析程序(“技术上简单”)和检查正确的语义(“任意困难”,考虑 C++)会导致生成不违反语言语义的随机程序的任何同样困难的问题。

      【讨论】:

      • 不错。一年过去了,然后没有任何评论就投了反对票。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多