/**
* Finds a pattern in a block of code.
*
* @param string $code
* @param string $pattern
* @param array $options The list of options to be used when parsing / matching `$code`:
* - 'ignore': An array of token names to ignore while parsing, defaults to
* `array('T_WHITESPACE')`
* - 'lineBreaks': If true, all tokens in a single pattern match must appear on the
* same line of code, defaults to false
* - 'startOfLine': If true, the pattern must match starting with the beginning of
* the line of code to be matched, defaults to false
* @return array
*/
public static function find($code, $pattern, $options = array())
{
$defaults = array('all' => true, 'capture' => array(), 'ignore' => array('T_WHITESPACE'), 'return' => true, 'lineBreaks' => false, 'startOfLine' => false);
$options += $defaults;
$results = array();
$matches = array();
$patternMatch = array();
$ret = $options['return'];
$tokens = new Collection(array('items' => static::tokenize($code, $options)));
$pattern = new Collection(array('items' => static::tokenize($pattern, $options)));
$breaks = function ($token) use(&$tokens, &$matches, &$patternMatch, $options) {
if (!$options['lineBreaks']) {
return true;
}
if (empty($patternMatch) && !$options['startOfLine']) {
return true;
}
if (empty($patternMatch)) {
$prev = $tokens->prev();
$tokens->next();
} else {
$prev = reset($patternMatch);
}
if (empty($patternMatch) && $options['startOfLine']) {
return $token['line'] > $prev['line'];
}
return $token['line'] == $prev['line'];
};
$capture = function ($token) use(&$matches, &$patternMatch, $tokens, $breaks, $options) {
if (is_null($token)) {
$matches = $patternMatch = array();
return false;
}
if (empty($patternMatch)) {
$prev = $tokens->prev();
$tokens->next();
if ($options['startOfLine'] && $token['line'] == $prev['line']) {
$patternMatch = $matches = array();
return false;
}
}
$patternMatch[] = $token;
if (empty($options['capture']) || !in_array($token['name'], $options['capture'])) {
return true;
}
if (!$breaks($token)) {
$matches = array();
return true;
}
$matches[] = $token;
return true;
};
$executors = array('*' => function (&$tokens, &$pattern) use($options, $capture) {
$closing = $pattern->next();
$tokens->prev();
while (($t = $tokens->next()) && !Parser::matchToken($closing, $t)) {
$capture($t);
}
$pattern->next();
});
$tokens->rewind();
$pattern->rewind();
while ($tokens->valid()) {
if (!$pattern->valid()) {
$pattern->rewind();
if (!empty($matches)) {
$results[] = array_map(function ($i) use($ret) {
return isset($i[$ret]) ? $i[$ret] : $i;
}, $matches);
}
$capture(null);
}
$p = $pattern->current();
$t = $tokens->current();
switch (true) {
case static::matchToken($p, $t):
$capture($t) ? $pattern->next() : $pattern->rewind();
break;
case isset($executors[$p['name']]):
$exec = $executors[$p['name']];
$exec($tokens, $pattern);
break;
default:
$capture(null);
$pattern->rewind();
break;
}
$tokens->next();
}
return $results;
}