Augmentation vers version 3.3.0

This commit is contained in:
Gauvain Boiché
2020-03-31 15:31:03 +02:00
parent d926806907
commit a1864c0414
2618 changed files with 406015 additions and 31377 deletions

View File

@@ -0,0 +1,148 @@
<?php
/**
* @package s9e\RegexpBuilder
* @copyright Copyright (c) 2016-2018 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\RegexpBuilder\Passes;
abstract class AbstractPass implements PassInterface
{
/**
* @var bool Whether the current set of strings is optional
*/
protected $isOptional;
/**
* {@inheritdoc}
*/
public function run(array $strings)
{
$strings = $this->beforeRun($strings);
if ($this->canRun($strings))
{
$strings = $this->runPass($strings);
}
$strings = $this->afterRun($strings);
return $strings;
}
/**
* Process the list of strings after the pass is run
*
* @param array[] $strings
* @return array[]
*/
protected function afterRun(array $strings)
{
if ($this->isOptional && $strings[0] !== [])
{
array_unshift($strings, []);
}
return $strings;
}
/**
* Prepare the list of strings before the pass is run
*
* @param array[] $strings
* @return array[]
*/
protected function beforeRun(array $strings)
{
$this->isOptional = (isset($strings[0]) && $strings[0] === []);
if ($this->isOptional)
{
array_shift($strings);
}
return $strings;
}
/**
* Test whether this pass can be run on a given list of strings
*
* @param array[] $strings
* @return bool
*/
protected function canRun(array $strings)
{
return true;
}
/**
* Run this pass on a list of strings
*
* @param array[] $strings
* @return array[]
*/
abstract protected function runPass(array $strings);
/**
* Test whether given string has an optional suffix
*
* @param array $string
* @return bool
*/
protected function hasOptionalSuffix(array $string)
{
$suffix = end($string);
return (is_array($suffix) && $suffix[0] === []);
}
/**
* Test whether given string contains a single alternations made of single values
*
* @param array $string
* @return bool
*/
protected function isCharacterClassString(array $string)
{
return ($this->isSingleAlternationString($string) && $this->isSingleCharStringList($string[0]));
}
/**
* Test whether given string contains one single element that is an alternation
*
* @param array
* @return bool
*/
protected function isSingleAlternationString(array $string)
{
return (count($string) === 1 && is_array($string[0]));
}
/**
* Test whether given string contains a single character value
*
* @param array $string
* @return bool
*/
protected function isSingleCharString(array $string)
{
return (count($string) === 1 && !is_array($string[0]));
}
/**
* Test whether given list of strings contains nothing but single-char strings
*
* @param array[] $strings
* @return bool
*/
protected function isSingleCharStringList(array $strings)
{
foreach ($strings as $string)
{
if (!$this->isSingleCharString($string))
{
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,138 @@
<?php
/**
* @package s9e\RegexpBuilder
* @copyright Copyright (c) 2016-2018 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\RegexpBuilder\Passes;
/**
* Replaces (?:ab?|b)? with a?b?
*/
class CoalesceOptionalStrings extends AbstractPass
{
/**
* {@inheritdoc}
*/
protected function canRun(array $strings)
{
return ($this->isOptional && count($strings) > 1);
}
/**
* {@inheritdoc}
*/
protected function runPass(array $strings)
{
foreach ($this->getPrefixGroups($strings) as $suffix => $prefixStrings)
{
$suffix = unserialize($suffix);
$suffixStrings = array_diff_key($strings, $prefixStrings);
if ($suffix === $this->buildSuffix($suffixStrings))
{
$this->isOptional = false;
return $this->buildCoalescedStrings($prefixStrings, $suffix);
}
}
return $strings;
}
/**
* Build the final list of coalesced strings
*
* @param array[] $prefixStrings
* @param array $suffix
* @return array[]
*/
protected function buildCoalescedStrings(array $prefixStrings, array $suffix)
{
$strings = $this->runPass($this->buildPrefix($prefixStrings));
if (count($strings) === 1 && $strings[0][0][0] === [])
{
// If the prefix has been remerged into a list of strings which contains only one string
// of which the first element is an optional alternations, we only need to append the
// suffix
$strings[0][] = $suffix;
}
else
{
// Put the current list of strings that form the prefix into a new list of strings, of
// which the only string is composed of our optional prefix followed by the suffix
array_unshift($strings, []);
$strings = [[$strings, $suffix]];
}
return $strings;
}
/**
* Build the list of strings used as prefix
*
* @param array[] $strings
* @return array[]
*/
protected function buildPrefix(array $strings)
{
$prefix = [];
foreach ($strings as $string)
{
// Remove the last element (suffix) of each string before adding it
array_pop($string);
$prefix[] = $string;
}
return $prefix;
}
/**
* Build a list of strings that matches any given strings or nothing
*
* Will unpack groups of single characters
*
* @param array[] $strings
* @return array[]
*/
protected function buildSuffix(array $strings)
{
$suffix = [[]];
foreach ($strings as $string)
{
if ($this->isCharacterClassString($string))
{
foreach ($string[0] as $element)
{
$suffix[] = $element;
}
}
else
{
$suffix[] = $string;
}
}
return $suffix;
}
/**
* Get the list of potential prefix strings grouped by identical suffix
*
* @param array[] $strings
* @return array
*/
protected function getPrefixGroups(array $strings)
{
$groups = [];
foreach ($strings as $k => $string)
{
if ($this->hasOptionalSuffix($string))
{
$groups[serialize(end($string))][$k] = $string;
}
}
return $groups;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* @package s9e\RegexpBuilder
* @copyright Copyright (c) 2016-2018 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\RegexpBuilder\Passes;
/**
* Replaces (?:ab|bb|c) with (?:[ab]b|c)
*/
class CoalesceSingleCharacterPrefix extends AbstractPass
{
/**
* {@inheritdoc}
*/
protected function runPass(array $strings)
{
$newStrings = [];
foreach ($this->getEligibleKeys($strings) as $keys)
{
// Create a new string to hold the merged strings and replace the first element with
// an empty character class
$newString = $strings[$keys[0]];
$newString[0] = [];
// Fill the character class with the prefix of each string in this group before removing
// the original string
foreach ($keys as $key)
{
$newString[0][] = [$strings[$key][0]];
unset($strings[$key]);
}
$newStrings[] = $newString;
}
return array_merge($newStrings, $strings);
}
/**
* Filter the list of eligible keys and keep those that have at least two matches
*
* @param array[] $eligibleKeys List of lists of keys
* @return array[]
*/
protected function filterEligibleKeys(array $eligibleKeys)
{
$filteredKeys = [];
foreach ($eligibleKeys as $k => $keys)
{
if (count($keys) > 1)
{
$filteredKeys[] = $keys;
}
}
return $filteredKeys;
}
/**
* Get a list of keys of strings eligible to be merged together, grouped by suffix
*
* @param array[] $strings
* @return array[]
*/
protected function getEligibleKeys(array $strings)
{
$eligibleKeys = [];
foreach ($strings as $k => $string)
{
if (!is_array($string[0]) && isset($string[1]))
{
$suffix = serialize(array_slice($string, 1));
$eligibleKeys[$suffix][] = $k;
}
}
return $this->filterEligibleKeys($eligibleKeys);
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* @package s9e\RegexpBuilder
* @copyright Copyright (c) 2016-2018 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\RegexpBuilder\Passes;
/**
* Enables other passes to replace (?:[xy]|a[xy]) with a?[xy]
*/
class GroupSingleCharacters extends AbstractPass
{
/**
* {@inheritdoc}
*/
protected function runPass(array $strings)
{
$singles = $this->getSingleCharStrings($strings);
$cnt = count($singles);
if ($cnt > 1 && $cnt < count($strings))
{
// Remove the singles from the input, then prepend them as a new string
$strings = array_diff_key($strings, $singles);
array_unshift($strings, [array_values($singles)]);
}
return $strings;
}
/**
* Return an array of every single-char string in given list of strings
*
* @param array[] $strings
* @return array[]
*/
protected function getSingleCharStrings(array $strings)
{
return array_filter($strings, [$this, 'isSingleCharString']);
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* @package s9e\RegexpBuilder
* @copyright Copyright (c) 2016-2018 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\RegexpBuilder\Passes;
/**
* Replaces (?:axx|ayy) with a(?:xx|yy)
*/
class MergePrefix extends AbstractPass
{
/**
* {@inheritdoc}
*/
protected function runPass(array $strings)
{
$newStrings = [];
foreach ($this->getStringsByPrefix($strings) as $prefix => $strings)
{
$newStrings[] = (isset($strings[1])) ? $this->mergeStrings($strings) : $strings[0];
}
return $newStrings;
}
/**
* Get the number of leading elements common to all given strings
*
* @param array[] $strings
* @return integer
*/
protected function getPrefixLength(array $strings)
{
$len = 1;
$cnt = count($strings[0]);
while ($len < $cnt && $this->stringsMatch($strings, $len))
{
++$len;
}
return $len;
}
/**
* Return given strings grouped by their first element
*
* NOTE: assumes that this pass is run before the first element of any string could be replaced
*
* @param array[] $strings
* @return array[]
*/
protected function getStringsByPrefix(array $strings)
{
$byPrefix = [];
foreach ($strings as $string)
{
$byPrefix[$string[0]][] = $string;
}
return $byPrefix;
}
/**
* Merge given strings into a new single string
*
* @param array[] $strings
* @return array
*/
protected function mergeStrings(array $strings)
{
$len = $this->getPrefixLength($strings);
$newString = array_slice($strings[0], 0, $len);
foreach ($strings as $string)
{
$newString[$len][] = array_slice($string, $len);
}
return $newString;
}
/**
* Test whether all given strings' elements match at given position
*
* @param array[] $strings
* @param integer $pos
* @return bool
*/
protected function stringsMatch(array $strings, $pos)
{
$value = $strings[0][$pos];
foreach ($strings as $string)
{
if (!isset($string[$pos]) || $string[$pos] !== $value)
{
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* @package s9e\RegexpBuilder
* @copyright Copyright (c) 2016-2018 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\RegexpBuilder\Passes;
/**
* Replaces (?:aax|bbx) with (?:aa|bb)x
*/
class MergeSuffix extends AbstractPass
{
/**
* {@inheritdoc}
*/
protected function canRun(array $strings)
{
return (count($strings) > 1 && $this->hasMatchingSuffix($strings));
}
/**
* {@inheritdoc}
*/
protected function runPass(array $strings)
{
$newString = [];
while ($this->hasMatchingSuffix($strings))
{
array_unshift($newString, end($strings[0]));
$strings = $this->pop($strings);
}
array_unshift($newString, $strings);
return [$newString];
}
/**
* Test whether all given strings have the same last element
*
* @param array[] $strings
* @return bool
*/
protected function hasMatchingSuffix(array $strings)
{
$suffix = end($strings[1]);
foreach ($strings as $string)
{
if (end($string) !== $suffix)
{
return false;
}
}
return ($suffix !== false);
}
/**
* Remove the last element of every string
*
* @param array[] $strings Original strings
* @return array[] Processed strings
*/
protected function pop(array $strings)
{
$cnt = count($strings);
$i = $cnt;
while (--$i >= 0)
{
array_pop($strings[$i]);
}
// Remove empty elements then prepend one back at the start of the array if applicable
$strings = array_filter($strings);
if (count($strings) < $cnt)
{
array_unshift($strings, []);
}
return $strings;
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* @package s9e\RegexpBuilder
* @copyright Copyright (c) 2016-2018 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\RegexpBuilder\Passes;
interface PassInterface
{
/**
* Run this pass
*
* @param array[] $strings Original strings
* @return array[] Modified strings
*/
public function run(array $strings);
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* @package s9e\RegexpBuilder
* @copyright Copyright (c) 2016-2018 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\RegexpBuilder\Passes;
/**
* Replaces alternations that only contain one string to allow other passes to replace
* (?:a0?x|bx) with (?:a0?|b)x
*/
class PromoteSingleStrings extends AbstractPass
{
/**
* {@inheritdoc}
*/
protected function runPass(array $strings)
{
return array_map([$this, 'promoteSingleStrings'], $strings);
}
/**
* Promote single strings found inside given string
*
* @param array $string Original string
* @return array Modified string
*/
protected function promoteSingleStrings(array $string)
{
$newString = [];
foreach ($string as $element)
{
if (is_array($element) && count($element) === 1)
{
$newString = array_merge($newString, $element[0]);
}
else
{
$newString[] = $element;
}
}
return $newString;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* @package s9e\RegexpBuilder
* @copyright Copyright (c) 2016-2018 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\RegexpBuilder\Passes;
use s9e\RegexpBuilder\Runner;
/**
* Enables passes to be run recursively into alternations to replace a(?:x0|x1|y0|y1) with a[xy][01]
*/
class Recurse extends AbstractPass
{
/**
* @var Runner
*/
protected $runner;
/**
* @param Runner $runner
*/
public function __construct(Runner $runner)
{
$this->runner = $runner;
}
/**
* {@inheritdoc}
*/
protected function runPass(array $strings)
{
return array_map([$this, 'recurseString'], $strings);
}
/**
* Recurse into given string and run all passes on each element
*
* @param array $string
* @return array
*/
protected function recurseString(array $string)
{
$isOptional = $this->isOptional;
foreach ($string as $k => $element)
{
if (is_array($element))
{
$string[$k] = $this->runner->run($element);
}
}
$this->isOptional = $isOptional;
return $string;
}
}