public function formatList($list)
{
/**
* The query to be returned.
*
* @var string $ret
*/
$ret = '';
/**
* The indentation level.
*
* @var int $indent
*/
$indent = 0;
/**
* Whether the line ended.
*
* @var bool $lineEnded
*/
$lineEnded = false;
/**
* Whether current group is short (no linebreaks)
*
* @var bool $shortGroup
*/
$shortGroup = false;
/**
* The name of the last clause.
*
* @var string $lastClause
*/
$lastClause = '';
/**
* A stack that keeps track of the indentation level every time a new
* block is found.
*
* @var array $blocksIndentation
*/
$blocksIndentation = array();
/**
* A stack that keeps track of the line endings every time a new block
* is found.
*
* @var array $blocksLineEndings
*/
$blocksLineEndings = array();
/**
* Whether clause's options were formatted.
*
* @var bool $formattedOptions
*/
$formattedOptions = false;
/**
* Previously parsed token.
*
* @var Token $prev
*/
$prev = null;
/**
* Comments are being formatted separately to maintain the whitespaces
* before and after them.
*
* @var string $comment
*/
$comment = '';
// In order to be able to format the queries correctly, the next token
// must be taken into consideration. The loop below uses two pointers,
// `$prev` and `$curr` which store two consecutive tokens.
// Actually, at every iteration the previous token is being used.
for ($list->idx = 0; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $curr
*/
$curr = $list->tokens[$list->idx];
if ($curr->type === Token::TYPE_WHITESPACE) {
// Whitespaces are skipped because the formatter adds its own.
continue;
} elseif ($curr->type === Token::TYPE_COMMENT) {
// Whether the comments should be parsed.
if (!empty($this->options['remove_comments'])) {
continue;
}
if ($list->tokens[$list->idx - 1]->type === Token::TYPE_WHITESPACE) {
// The whitespaces before and after are preserved for
// formatting reasons.
$comment .= $list->tokens[$list->idx - 1]->token;
}
$comment .= $this->toString($curr);
if ($list->tokens[$list->idx + 1]->type === Token::TYPE_WHITESPACE && $list->tokens[$list->idx + 2]->type !== Token::TYPE_COMMENT) {
// Adding the next whitespace only there is no comment that
// follows it immediately which may cause adding a
// whitespace twice.
$comment .= $list->tokens[$list->idx + 1]->token;
}
// Everything was handled here, no need to continue.
continue;
}
// Checking if pointers were initialized.
if ($prev !== null) {
// Checking if a new clause started.
if (static::isClause($prev) !== false) {
$lastClause = $prev->value;
$formattedOptions = false;
}
// The options of a clause should stay on the same line and everything that follows.
if ($this->options['parts_newline'] && !$formattedOptions && empty(self::$INLINE_CLAUSES[$lastClause]) && ($curr->type !== Token::TYPE_KEYWORD || $curr->type === Token::TYPE_KEYWORD && $curr->flags & Token::FLAG_KEYWORD_FUNCTION)) {
$formattedOptions = true;
$lineEnded = true;
++$indent;
}
// Checking if this clause ended.
if ($tmp = static::isClause($curr)) {
if ($tmp == 2 || $this->options['clause_newline']) {
$lineEnded = true;
if ($this->options['parts_newline']) {
--$indent;
}
}
}
// Indenting BEGIN ... END blocks.
if ($prev->type === Token::TYPE_KEYWORD && $prev->value === 'BEGIN') {
$lineEnded = true;
array_push($blocksIndentation, $indent);
++$indent;
} elseif ($curr->type === Token::TYPE_KEYWORD && $curr->value === 'END') {
$lineEnded = true;
$indent = array_pop($blocksIndentation);
}
// Formatting fragments delimited by comma.
if ($prev->type === Token::TYPE_OPERATOR && $prev->value === ',') {
// Fragments delimited by a comma are broken into multiple
// pieces only if the clause is not inlined or this fragment
// is between brackets that are on new line.
if (empty(self::$INLINE_CLAUSES[$lastClause]) && !$shortGroup && $this->options['parts_newline'] || end($blocksLineEndings) === true) {
$lineEnded = true;
}
}
// Handling brackets.
// Brackets are indented only if the length of the fragment between
// them is longer than 30 characters.
if ($prev->type === Token::TYPE_OPERATOR && $prev->value === '(') {
array_push($blocksIndentation, $indent);
$shortGroup = true;
if (static::getGroupLength($list) > 30) {
++$indent;
$lineEnded = true;
$shortGroup = false;
}
array_push($blocksLineEndings, $lineEnded);
} elseif ($curr->type === Token::TYPE_OPERATOR && $curr->value === ')') {
$indent = array_pop($blocksIndentation);
$lineEnded |= array_pop($blocksLineEndings);
$shortGroup = false;
}
// Delimiter must be placed on the same line with the last
// clause.
if ($curr->type === Token::TYPE_DELIMITER) {
$lineEnded = false;
}
// Adding the token.
$ret .= $this->toString($prev);
// Finishing the line.
if ($lineEnded) {
if ($indent < 0) {
// TODO: Make sure this never occurs and delete it.
$indent = 0;
}
if ($curr->type !== Token::TYPE_COMMENT) {
$ret .= $this->options['line_ending'] . str_repeat($this->options['indentation'], $indent);
}
$lineEnded = false;
} else {
// If the line ended there is no point in adding whitespaces.
// Also, some tokens do not have spaces before or after them.
if (!($prev->type === Token::TYPE_OPERATOR && ($prev->value === '.' || $prev->value === '(') || $curr->type === Token::TYPE_OPERATOR && ($curr->value === '.' || $curr->value === ',' || $curr->value === '(' || $curr->value === ')') || $curr->type === Token::TYPE_DELIMITER && mb_strlen($curr->value, 'UTF-8') < 2) || $prev->value === 'DELIMITER') {
$ret .= ' ';
}
}
}
if (!empty($comment)) {
$ret .= $comment;
$comment = '';
}
// Iteration finished, consider current token as previous.
$prev = $curr;
}
if ($this->options['type'] === 'cli') {
return $ret . "[0m";
}
return $ret;
}