首页 > 解决方案 > 具有成对限制的列表的 FsCheck 生成器 (C#)

问题描述

在 C# 中使用 FsCheck,我需要生成一个值列表,其中某些值可能不会相邻出现。这是词法分析器的标记列表。例如,我不需要生成两个相邻的标识符,或者一个关键字旁边的标识符,因为当它们变成字符串时,词法分析器会将它们视为一个标记。我不能简单地做这样的事情:

Gen.ListOf(Gen.Elements("fizz", "buzz", "bazz", "+", " ", "if")).Where(list => ...);

在一个合理长度的列表中,很可能有两个不应该相邻的标记。因此过滤器会丢弃太多的值,生成会很慢。我考虑过添加或删除标记来尝试修复列表,但这似乎非常复杂,我担心它会生成太短或太长的列表。

我想要做的是“一次一个元素”生成列表。所以我会随机选择长度,然后随机选择每个元素是否允许跟随前面的元素,直到我得到完整的列表。这似乎应该是递归可行的,但我不知道如何用生成器组合器来表达它。有没有办法做到这一点?我希望我可以编写一个任意的 lambda,它可以直接调用其中的生成器,而不需要编写它们。这样的事情可能吗?

注意:我已经简化了示例。令牌生成要复杂得多。

标签: c#fscheck

解决方案


假设你有identifier两种keyword类型Gen<string>,你可以做类似 (C#-y 伪代码)

var any = Gen.Elements (new[] {identifier, keyword});

Gen<ImmutableList<string>> Generate(int length, Gen<ImmutableList<string>> soFar) {
    if (length == 0) return soFar;

    var result = soFar.SelectMany(l =>  {
        // after a keyword anything goes, after an identifier must follow a keyword    
        var noId = ... // check what last token in l is
        var next = noId ? keyword : any;
        return next.SelectMany(n => l.Add(n));
    });

    return Generate(length-1, result);
}

您还可以创建一个枚举值 + 字符串列表,以便枚举值定义您正在生成的令牌类型(从您刚刚生成的文本中找出令牌的类型对我来说似乎有点奇怪......这听起来也很奇怪就像它正是您要测试的代码一样)。

一般来说,我可能会建议一种不同的方法,这取决于你有多少令牌以及所有的约束是什么。在这种情况下,通常更容易定义一些“模式”,即合法的令牌列表,您可以一个接一个地串而不中断(太多,可能在工作后进行另一个过滤器)。一个例子(在 F# 中)在这里:https ://github.com/fsprojects/fantomas/blob/master/src/Fantomas.Tests/FormattingPropertyTests.fs

这些模式通常最终成为您的语法或句法树,因此基本结构应该很简单。


推荐阅读