You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
6.0 KiB
PHTML
234 lines
6.0 KiB
PHTML
5 years ago
|
<?php
|
||
|
|
||
|
/* vim: set shiftwidth=2 expandtab softtabstop=2: */
|
||
|
|
||
|
namespace Boris;
|
||
|
|
||
|
/**
|
||
|
* The ShallowParser takes whatever is currently buffered and chunks it into individual statements.
|
||
|
*/
|
||
|
class ShallowParser {
|
||
|
private $_pairs = array(
|
||
|
'(' => ')',
|
||
|
'{' => '}',
|
||
|
'[' => ']',
|
||
|
'"' => '"',
|
||
|
"'" => "'",
|
||
|
'//' => "\n",
|
||
|
'#' => "\n",
|
||
|
'/*' => '*/',
|
||
|
'<<<' => '_heredoc_special_case_'
|
||
|
);
|
||
|
|
||
|
private $_initials;
|
||
|
|
||
|
public function __construct() {
|
||
|
$this->_initials = '/^(' . implode('|', array_map(array($this, 'quote'), array_keys($this->_pairs))) . ')/';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Break the $buffer into chunks, with one for each highest-level construct possible.
|
||
|
*
|
||
|
* If the buffer is incomplete, returns an empty array.
|
||
|
*
|
||
|
* @param string $buffer
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
public function statements($buffer) {
|
||
|
$result = $this->_createResult($buffer);
|
||
|
|
||
|
while (strlen($result->buffer) > 0) {
|
||
|
$this->_resetResult($result);
|
||
|
|
||
|
if ($result->state == '<<<') {
|
||
|
if (!$this->_initializeHeredoc($result)) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$rules = array('_scanEscapedChar', '_scanRegion', '_scanStateEntrant', '_scanWsp', '_scanChar');
|
||
|
|
||
|
foreach ($rules as $method) {
|
||
|
if ($this->$method($result)) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($result->stop) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!empty($result->statements) && trim($result->stmt) === '' && strlen($result->buffer) == 0) {
|
||
|
$this->_combineStatements($result);
|
||
|
$this->_prepareForDebug($result);
|
||
|
return $result->statements;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function quote($token) {
|
||
|
return preg_quote($token, '/');
|
||
|
}
|
||
|
|
||
|
// -- Private Methods
|
||
|
|
||
|
private function _createResult($buffer) {
|
||
|
$result = new \stdClass();
|
||
|
$result->buffer = $buffer;
|
||
|
$result->stmt = '';
|
||
|
$result->state = null;
|
||
|
$result->states = array();
|
||
|
$result->statements = array();
|
||
|
$result->stop = false;
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
private function _resetResult($result) {
|
||
|
$result->stop = false;
|
||
|
$result->state = end($result->states);
|
||
|
$result->terminator = $result->state
|
||
|
? '/^(.*?' . preg_quote($this->_pairs[$result->state], '/') . ')/s'
|
||
|
: null
|
||
|
;
|
||
|
}
|
||
|
|
||
|
private function _combineStatements($result) {
|
||
|
$combined = array();
|
||
|
|
||
|
foreach ($result->statements as $scope) {
|
||
|
if (trim($scope) == ';' || substr(trim($scope), -1) != ';') {
|
||
|
$combined[] = ((string) array_pop($combined)) . $scope;
|
||
|
} else {
|
||
|
$combined[] = $scope;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$result->statements = $combined;
|
||
|
}
|
||
|
|
||
|
private function _prepareForDebug($result) {
|
||
|
$result->statements []= $this->_prepareDebugStmt(array_pop($result->statements));
|
||
|
}
|
||
|
|
||
|
private function _initializeHeredoc($result) {
|
||
|
if (preg_match('/^([\'"]?)([a-z_][a-z0-9_]*)\\1/i', $result->buffer, $match)) {
|
||
|
$docId = $match[2];
|
||
|
$result->stmt .= $match[0];
|
||
|
$result->buffer = substr($result->buffer, strlen($match[0]));
|
||
|
|
||
|
$result->terminator = '/^(.*?\n' . $docId . ');?\n/s';
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function _scanWsp($result) {
|
||
|
if (preg_match('/^\s+/', $result->buffer, $match)) {
|
||
|
if (!empty($result->statements) && $result->stmt === '') {
|
||
|
$result->statements[] = array_pop($result->statements) . $match[0];
|
||
|
} else {
|
||
|
$result->stmt .= $match[0];
|
||
|
}
|
||
|
$result->buffer = substr($result->buffer, strlen($match[0]));
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function _scanEscapedChar($result) {
|
||
|
if (($result->state == '"' || $result->state == "'")
|
||
|
&& preg_match('/^[^' . $result->state . ']*?\\\\./s', $result->buffer, $match)) {
|
||
|
|
||
|
$result->stmt .= $match[0];
|
||
|
$result->buffer = substr($result->buffer, strlen($match[0]));
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function _scanRegion($result) {
|
||
|
if (in_array($result->state, array('"', "'", '<<<', '//', '#', '/*'))) {
|
||
|
if (preg_match($result->terminator, $result->buffer, $match)) {
|
||
|
$result->stmt .= $match[1];
|
||
|
$result->buffer = substr($result->buffer, strlen($match[1]));
|
||
|
array_pop($result->states);
|
||
|
} else {
|
||
|
$result->stop = true;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function _scanStateEntrant($result) {
|
||
|
if (preg_match($this->_initials, $result->buffer, $match)) {
|
||
|
$result->stmt .= $match[0];
|
||
|
$result->buffer = substr($result->buffer, strlen($match[0]));
|
||
|
$result->states[] = $match[0];
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function _scanChar($result) {
|
||
|
$chr = substr($result->buffer, 0, 1);
|
||
|
$result->stmt .= $chr;
|
||
|
$result->buffer = substr($result->buffer, 1);
|
||
|
if ($result->state && $chr == $this->_pairs[$result->state]) {
|
||
|
array_pop($result->states);
|
||
|
}
|
||
|
|
||
|
if (empty($result->states) && ($chr == ';' || $chr == '}')) {
|
||
|
if (!$this->_isLambda($result->stmt) || $chr == ';') {
|
||
|
$result->statements[] = $result->stmt;
|
||
|
$result->stmt = '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private function _isLambda($input) {
|
||
|
return preg_match(
|
||
|
'/^([^=]*?=\s*)?function\s*\([^\)]*\)\s*(use\s*\([^\)]*\)\s*)?\s*\{.*\}\s*;?$/is',
|
||
|
trim($input)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function _isReturnable($input) {
|
||
|
$input = trim($input);
|
||
|
if (substr($input, -1) == ';' && substr($input, 0, 1) != '{') {
|
||
|
return $this->_isLambda($input) || !preg_match(
|
||
|
'/^(' .
|
||
|
'echo|print|exit|die|goto|global|include|include_once|require|require_once|list|' .
|
||
|
'return|do|for|foreach|while|if|function|namespace|class|interface|abstract|switch|' .
|
||
|
'declare|throw|try|unset' .
|
||
|
')\b/i',
|
||
|
$input
|
||
|
);
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function _prepareDebugStmt($input) {
|
||
|
if ($this->_isReturnable($input) && !preg_match('/^\s*return/i', $input)) {
|
||
|
$input = sprintf('return %s', $input);
|
||
|
}
|
||
|
|
||
|
return $input;
|
||
|
}
|
||
|
}
|