Skip to content

About

[READ-ONLY] Generates sample strings and test cases that a regex matches or rejects. Split of php-regex/php-regex.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

PHPRegex Generator

Documentation Badge Author Badge GitHub Release Badge License Badge Packagist Downloads Badge GitHub Stars Badge Supported PHP Version Badge

PHPRegex Generator

Generates sample strings and test cases that a regex matches or rejects.

Part of PHPRegex, released with its siblings under one version number.

Documentation: php-regex.com — the API reference documents generate() end to end.

Features

  • Generates a random string the pattern matches, from the parsed AST, and checks it with the running engine
  • Reproducible samples: seed the generator, reset the seed, draw again
  • Open-ended quantifiers (*, +, {n,}) capped by a configurable repetition limit
  • Unicode properties (\p{Lu}), POSIX classes ([[:alpha:]]) and negated classes resolved by asking the running PCRE engine
  • Lookaheads and lookbehinds held where they stand; backreferences repeat what their group captured
  • Matching and non-matching strings for a pattern, ready for a test suite
  • SampleGenerationException when no sample can be built

Installation

composer require php-regex/regex-generator

Requires PHP 8.1+ and php-regex/regex-parser. MIT licensed.

Configuration

  • maxRepetition (int, default 3): extra repetitions drawn for *, + and {n,}
  • seed (?int, default null, last argument of the SampleGenerator constructor): locks the random source for reproducible samples; each sample drawn starts again from it
  • setSeed(int $seed) and resetSeed(): lock the random source later, or unlock it
  • PcreEngine $engine (constructor of both generators): the engine asked what a class or a property holds

Usage

A sample the running engine matches: generate() draws from the pattern and checks each draw with preg_match(), padding the sample when an assertion asks for text around it, and throws SampleGenerationException when no draw matches:

use PHPRegex\Generator\SampleGenerator;

echo (new SampleGenerator())->generate('/(?!^)a/'); // "aa"

A sample is at most 1,048,576 bytes, and so is every piece built on the way to it, a lookahead's text and a padded candidate included: a pattern whose backreferences double the text at each step, /(a)(\g{1}\g{1})(\g{2}\g{2})…/, throws SampleGenerationException ("No sample was built: it would pass 1048576 bytes.") before the string is built. One candidate can go further: a group fitted to substring scans, (*scs:…), joins a drawn prefix under the limit to its text for each scan that reads it, and checks the candidate against the group and the scan before the sample refuses it, so a group that two scans read can reach three times the limit.

A lookahead draws what follows it again, so lookaheads in a row nest: at most 128 deep, and at most 131,072 sequence entries copied for one sample. Past either, SampleGenerationException says which limit was met. A plain repetition copies nothing and is not charged: /^(?:a.)+$/ repeated 37,329 times builds its 74,658-byte sample.

A repeat that adds text is bounded by the length of the sample; one that adds none, as a reference to a group that captured nothing, is counted apart, 1,048,576 of them over the draws of one sample. /()(?:\1{65535}){100}/, which PCRE compiles, throws SampleGenerationException ("No sample was built: more than 1048576 repeats added nothing to it.") where it went through 6,553,500 repeats of nothing; /a{65535}b{65535}/ builds its 131,070 bytes as before.

Visiting the tree draws from the tree alone, unchecked; a sample, made reproducible with a seed:

use PHPRegex\Generator\SampleGenerator;
use PHPRegex\Parser\RegexParser;

$generator = new SampleGenerator(seed: 42);

echo RegexParser::create()->parse('/[A-Z]{3}-\d{4}/')->accept($generator); // "VEL-0403"

Test cases, matching and not, each one checked against the running engine (preg_match() gives 1 on every matching case, 0 on every non-matching one):

use PHPRegex\Generator\TestCaseGenerator;
use PHPRegex\Parser\RegexParser;

$cases = RegexParser::create()->parse('/\d{3}-\d{4}/')->accept(new TestCaseGenerator());

echo $cases['matching'][0];     // "000-0000"
echo $cases['non_matching'][0]; // "00.000"

When no sample can be built, the visitor says why:

use PHPRegex\Generator\Exception\SampleGenerationException;
use PHPRegex\Generator\SampleGenerator;
use PHPRegex\Parser\RegexParser;

try {
    RegexParser::create()->parse('/(?&missing)/')->accept(new SampleGenerator());
} catch (SampleGenerationException $e) {
    echo $e->getMessage(); // "Sample generation for subroutines is not supported."
}

The Toolkit facade checks each sample against the running engine and retries before settling.

Documentation

Resources

Sponsors

Sponsor

If PHPRegex saves you time, consider sponsoring its maintenance.

License

MIT. See LICENSE.

About

[READ-ONLY] Generates sample strings and test cases that a regex matches or rejects. Split of php-regex/php-regex.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages