Генератор FsCheck для списка с попарными ограничениями (C #) - PullRequest
0 голосов
/ 07 октября 2018

Используя FsCheck в C #, мне нужно сгенерировать список значений, где определенные значения могут не появляться рядом друг с другом.Это список токенов для лексера.Так, например, мне не нужно генерировать два идентификатора рядом друг с другом или ключевое слово рядом с идентификатором, потому что, когда они превращаются в строку, лексер будет видеть их как один токен.Я не могу просто сделать что-то вроде:

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

В списке разумной длины очень вероятно, что рядом друг с другом есть два токена, которых не должно быть.Таким образом, фильтр отбрасывает слишком много значений, и генерация будет очень медленной.Я рассмотрел добавление или удаление токенов, чтобы попытаться исправить список, но это кажется очень сложным, и я боюсь, что это приведет к тому, что списки будут слишком короткими или слишком длинными.

То, что я хочу иметь возможностьсделать, это создать список «один элемент за один раз».Поэтому я выбирал случайным образом длину, а затем произвольно выбирал каждый элемент в зависимости от того, разрешено ли ему следовать за предыдущим элементом, пока у меня не будет полного списка.Кажется, что это должно быть выполнимо рекурсивно, но я не могу понять, как выразить это с помощью комбинаторов генератора.Есть способ сделать это?Я бы хотел написать произвольную лямбду, которая могла бы напрямую вызывать генераторы внутри нее, а не составлять их.Возможно ли что-то подобное?

Примечание. Я упростил пример.Генерация токенов намного сложнее.

1 Ответ

0 голосов
/ 23 октября 2018

Скажем, у вас есть 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);
}

Вы также можете создать список значений enum +строка, так что значение enum определяет, какой токен вы генерируете (мне кажется странным выяснять тип токена из текста, который вы только что сгенерировали ... это также звучит так, как если бы это был именно тот код, который вы пытаетесьдля тестирования).

В общем, я бы посоветовал другой подход, в зависимости от того, сколько у вас токенов и каковы все ограничения.В таких случаях часто проще определить ряд «шаблонов», то есть списков токенов, которые являются законными и которые вы можете натягивать один за другим, не разрывая (слишком много, возможно, делая другой фильтр после работы).Пример этого (в F #) приведен здесь: https://github.com/fsprojects/fantomas/blob/master/src/Fantomas.Tests/FormattingPropertyTests.fs

Эти шаблоны, как правило, в конечном итоге становятся вашим грамматическим или синтаксическим деревом, поэтому базовая структура должна быть простой.

...