Completed
Branch CoalesceOptionalStrings (db448d)
by Josh
02:42
created

CoalesceOptionalStrings::buildCoalescedStrings()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 2
nop 2
crap 3
1
<?php
2
3
/**
4
* @package   s9e\RegexpBuilder
5
* @copyright Copyright (c) 2016 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\RegexpBuilder\Passes;
9
10
/**
11
* Replaces (?:ab?|b)? with a?b?
12
*/
13
class CoalesceOptionalStrings extends AbstractPass
14
{
15
	/**
16
	* {@inheritdoc}
17
	*/
18 7
	protected function canRun(array $strings)
19
	{
20 7
		return ($this->isOptional && count($strings) > 1);
21
	}
22
23
	/**
24
	* {@inheritdoc}
25
	*/
26 7
	protected function runPass(array $strings)
27
	{
28 7
		foreach ($this->getPrefixGroups($strings) as $suffix => $prefixStrings)
29
		{
30 7
			$suffix        = unserialize($suffix);
31 7
			$suffixStrings = array_diff_key($strings, $prefixStrings);
32 7
			if ($suffix === $this->buildSuffix($suffixStrings))
33
			{
34 4
				$this->isOptional = false;
35
36 7
				return $this->buildCoalescedStrings($prefixStrings, $suffix);
37
			}
38
		}
39
40 7
		return $strings;
41
	}
42
43
	/**
44
	* Build the final list of coalesced strings
45
	*
46
	* @param  array[] $prefixStrings
47
	* @param  array   $suffix
48
	* @return array[]
49
	*/
50 4
	protected function buildCoalescedStrings(array $prefixStrings, array $suffix)
51
	{
52 4
		$strings = $this->runPass($this->buildPrefix($prefixStrings));
53 4
		if (count($strings) === 1 && $strings[0][0][0] === [])
54
		{
55
			// If the prefix has been remerged into a list of strings which contains only one string
56
			// of which the first element is an optional alternations, we only need to append the
57
			// suffix
58 2
			$strings[0][] = $suffix;
59
		}
60
		else
61
		{
62
			// Put the current list of strings that form the prefix into a new list of strings, of
63
			// which the only string is composed of our optional prefix followed by the suffix
64 4
			array_unshift($strings, []);
65 4
			$strings = [[$strings, $suffix]];
66
		}
67
68 4
		return $strings;
69
	}
70
71
	/**
72
	* Build the list of strings used as prefix
73
	*
74
	* @param  array[] $strings
75
	* @return array[]
76
	*/
77 4
	protected function buildPrefix(array $strings)
78
	{
79 4
		$prefix = [];
80 4
		foreach ($strings as $string)
81
		{
82
			// Remove the last element (suffix) of each string before adding it
83 4
			array_pop($string);
84 4
			$prefix[] = $string;
85
		}
86
87 4
		return $prefix;
88
	}
89
90
	/**
91
	* Build a list of strings that matches any given strings or nothing
92
	*
93
	* Will unpack groups of single characters
94
	*
95
	* @param  array[] $strings
96
	* @return array[]
97
	*/
98 7
	protected function buildSuffix(array $strings)
99
	{
100 7
		$suffix = [[]];
101 7
		foreach ($strings as $string)
102
		{
103 7
			if ($this->isCharacterClassString($string))
104
			{
105 1
				foreach ($string[0] as $element)
106
				{
107 1
					$suffix[] = $element;
108
				}
109
			}
110
			else
111
			{
112 7
				$suffix[] = $string;
113
			}
114
		}
115
116 7
		return $suffix;
117
	}
118
119
	/**
120
	* Get the list of potential prefix strings grouped by identical suffix
121
	*
122
	* @param  array[] $strings
123
	* @return array
124
	*/
125 7
	protected function getPrefixGroups(array $strings)
126
	{
127 7
		$groups = [];
128 7
		foreach ($strings as $k => $string)
129
		{
130 7
			if ($this->hasOptionalSuffix($string))
131
			{
132 7
				$groups[serialize(end($string))][$k] = $string;
133
			}
134
		}
135
136 7
		return $groups;
137
	}
138
139
	/**
140
	* Test whether given string has an optional suffix
141
	*
142
	* @param  array $string
143
	* @return bool
144
	*/
145 7
	protected function hasOptionalSuffix(array $string)
146
	{
147 7
		$suffix = end($string);
148
149 7
		return (is_array($suffix) && $suffix[0] === []);
150
	}
151
152
	/**
153
	* Test whether given string contains a single alternations made of single values
154
	*
155
	* @param  array $string
156
	* @return bool
157
	*/
158 7
	protected function isCharacterClassString(array $string)
159
	{
160 7
		if (count($string) !== 1 || !is_array($string[0]))
161
		{
162 7
			return false;
163
		}
164 1
		foreach ($string[0] as $substring)
165
		{
166 1
			if (!$this->isSingleCharString($substring))
167
			{
168 1
				return false;
169
			}
170
		}
171
172 1
		return true;
173
	}
174
}