您可以通过滥用解析器将其变成生成器来做到这一点。
首先,为您的语言构建一个递归解析器。 (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++)会导致生成不违反语言语义的随机程序的任何同样困难的问题。