From b80f326b6a1ec3c34a3851f38f39077954811f22 Mon Sep 17 00:00:00 2001 From: nikic <+@ni-po.com> Date: Thu, 2 Jun 2011 22:52:24 +0200 Subject: [PATCH] Fix problem with indented strings by introducing PrettyPrinter->pSafe --- README.md | 5 +- lib/PrettyPrinter/Zend.php | 56 +++++++++------------ lib/PrettyPrinterAbstract.php | 91 ++++++++++++++++++++--------------- test/testAgainstDirectory.php | 2 +- 4 files changed, 79 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 07d8db0..ba5e596 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ The pretty printer compiles nodes back to PHP code. "Pretty printing" here is ju name of the process and does not mean that the output is in any way pretty. $prettyPrinter = new PrettyPrinter_Zend; - echo '
' . htmlspecialchars($prettyPrinter->pStmts($stmts)) . ''; + echo '
' . htmlspecialchars($prettyPrinter->prettyPrint($stmts)) . ''; For the code mentioned in the above section this should create the output: @@ -151,6 +151,3 @@ For the code mentioned in the above section this should create the output: Known Issues ============ - - * When pretty printing strings and InlineHTML those are indented like any other code, thus causing - extra whitespace to be inserted (causes 23 compare fails in test against Symfony Beta 3). \ No newline at end of file diff --git a/lib/PrettyPrinter/Zend.php b/lib/PrettyPrinter/Zend.php index feae184..2419444 100644 --- a/lib/PrettyPrinter/Zend.php +++ b/lib/PrettyPrinter/Zend.php @@ -53,7 +53,7 @@ class PrettyPrinter_Zend extends PrettyPrinterAbstract public function pScalar_String(Node_Scalar_String $node) { return ($node->isBinary ? 'b' : '') . (Node_Scalar_String::SINGLE_QUOTED === $node->type - ? '\'' . addcslashes($node->value, '\'\\') . '\'' + ? '\'' . $this->pSafe(addcslashes($node->value, '\'\\')) . '\'' : '"' . addcslashes($node->value, "\n\r\t\f\v$\"\\") . '"' ); } @@ -394,7 +394,7 @@ class PrettyPrinter_Zend extends PrettyPrinterAbstract return 'function ' . ($node->byRef ? '&' : '') . '(' . $this->pCommaSeparated($node->params) . ')' . (!empty($node->useVars) ? ' use(' . $this->pCommaSeparated($node->useVars) . ')': '') - . ' {' . "\n" . $this->pIndent($this->pStmts($node->stmts)) . '}'; + . ' {' . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pExpr_LambdaFuncUse(Node_Expr_LambdaFuncUse $node) { @@ -436,7 +436,7 @@ class PrettyPrinter_Zend extends PrettyPrinterAbstract public function pStmt_Interface(Node_Stmt_Interface $node) { return 'interface ' . $node->name . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '') - . "\n" . '{' . "\n" . $this->pIndent($this->pStmts($node->stmts)) . '}'; + . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pStmt_Class(Node_Stmt_Class $node) { @@ -444,7 +444,7 @@ class PrettyPrinter_Zend extends PrettyPrinterAbstract . 'class ' . $node->name . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '') . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') - . "\n" . '{' . "\n" . $this->pIndent($this->pStmts($node->stmts)) . '}'; + . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pStmt_Property(Node_Stmt_Property $node) { @@ -461,7 +461,7 @@ class PrettyPrinter_Zend extends PrettyPrinterAbstract . 'function ' . ($node->byRef ? '&' : '') . $node->name . '(' . $this->pCommaSeparated($node->params) . ')' . (null !== $node->stmts - ? "\n" . '{' . "\n" . $this->pIndent($this->pStmts($node->stmts)) . '}' + ? "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}' : ';'); } @@ -475,8 +475,8 @@ class PrettyPrinter_Zend extends PrettyPrinterAbstract public function pStmt_Func(Node_Stmt_Func $node) { return 'function ' . ($node->byRef ? '&' : '') . $node->name - . '(' . $this->pCommaSeparated($node->params) . ')' . "\n" . '{' . "\n" - . $this->pIndent($this->pStmts($node->stmts)) . '}'; + . '(' . $this->pCommaSeparated($node->params) . ')' + . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pStmt_FuncParam(Node_Stmt_FuncParam $node) { @@ -497,20 +497,19 @@ class PrettyPrinter_Zend extends PrettyPrinterAbstract // Control flow public function pStmt_If(Node_Stmt_If $node) { - return 'if (' . $this->p($node->cond) . ') {' . "\n" - . $this->pIndent($this->pStmts($node->stmts)) . '}' + return 'if (' . $this->p($node->cond) . ') {' + . "\n" . $this->pStmts($node->stmts) . "\n" . '}' . $this->pImplode($node->elseifList) . (null !== $node->else ? $this->p($node->else) : ''); } public function pStmt_Elseif(Node_Stmt_Elseif $node) { - return ' elseif (' . $this->p($node->cond) . ') {' . "\n" - . $this->pIndent($this->pStmts($node->stmts)) . '}'; + return ' elseif (' . $this->p($node->cond) . ') {' + . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pStmt_Else(Node_Stmt_Else $node) { - return ' else {' . "\n" - . $this->pIndent($this->pStmts($node->stmts)) . '}'; + return ' else {' . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pStmt_For(Node_Stmt_For $node) { @@ -518,44 +517,44 @@ class PrettyPrinter_Zend extends PrettyPrinterAbstract . $this->pCommaSeparated($node->init) . ';' . $this->pCommaSeparated($node->cond) . ';' . $this->pCommaSeparated($node->loop) - . ') {' . "\n" . $this->pIndent($this->pStmts($node->stmts)) . '}'; + . ') {' . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pStmt_Foreach(Node_Stmt_Foreach $node) { return 'foreach (' . $this->p($node->expr) . ' as ' . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '') - . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {' . "\n" - . $this->pIndent($this->pStmts($node->stmts)) . '}'; + . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {' + . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pStmt_While(Node_Stmt_While $node) { return 'while (' . $this->p($node->cond) . ') {' - . "\n" . $this->pIndent($this->pStmts($node->stmts)) . '}'; + . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pStmt_Do(Node_Stmt_Do $node) { - return 'do {' . "\n" . $this->pIndent($this->pStmts($node->stmts)) - . '} while (' . $this->p($node->cond) . ')'; + return 'do {' . "\n" . $this->pStmts($node->stmts) + . '} while (' . $this->p($node->cond) . "\n" . ')'; } public function pStmt_Switch(Node_Stmt_Switch $node) { return 'switch (' . $this->p($node->cond) . ') {' - . "\n" . $this->pIndent($this->pImplode($node->caseList)) . '}'; + . "\n" . $this->pImplode($node->caseList) . '}'; } public function pStmt_TryCatch(Node_Stmt_TryCatch $node) { - return 'try {' . "\n" . $this->pIndent($this->pStmts($node->stmts)) . '}' + return 'try {' . "\n" . $this->pStmts($node->stmts) . "\n" . '}' . $this->pImplode($node->catches); } public function pStmt_Catch(Node_Stmt_Catch $node) { return ' catch (' . $this->p($node->type) . ' $' . $node->var . ') {' - . "\n" . $this->pIndent($this->pStmts($node->stmts)) . '}'; + . "\n" . $this->pStmts($node->stmts) . "\n" . '}'; } public function pStmt_Case(Node_Stmt_Case $node) { return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':' - . "\n" . $this->pIndent($this->pStmts($node->stmts)); + . "\n" . $this->pStmts($node->stmts) . "\n"; } public function pStmt_Break(Node_Stmt_Break $node) { @@ -606,7 +605,8 @@ class PrettyPrinter_Zend extends PrettyPrinterAbstract } public function pStmt_InlineHTML(Node_Stmt_InlineHTML $node) { - return '?>' . ("\n" === $node->value[0] ? "\n" : '') . $node->value . '' . ("\n" === $node->value[0] || "\r" === $node->value[0] ? "\n" : '') + . $this->pSafe($node->value) . ' $element) { if (is_string($element)) { $return .= addcslashes($element, "\n\r\t\f\v$\"\\"); - } elseif ($element instanceof Node_Variable - && is_string($element->name) - && (!isset($encapsList[$i + 1]) - || !is_string($encapsList[$i + 1]) - || '[' !== $encapsList[$i + 1][0] - ) - ) { - $return .= '$' . $element->name; } else { $return .= '{' . $this->p($element) . '}'; } diff --git a/lib/PrettyPrinterAbstract.php b/lib/PrettyPrinterAbstract.php index c3b330c..ac7e5f5 100644 --- a/lib/PrettyPrinterAbstract.php +++ b/lib/PrettyPrinterAbstract.php @@ -56,40 +56,62 @@ abstract class PrettyPrinterAbstract 'Expr_LogicalXor' => 17, 'Expr_LogicalOr' => 18, ); + protected $stmtsWithoutSemicolon = array( + 'Stmt_Func' => true, + 'Stmt_Interface' => true, + 'Stmt_Class' => true, + 'Stmt_ClassMethod' => true, + 'Stmt_For' => true, + 'Stmt_Foreach' => true, + 'Stmt_If' => true, + 'Stmt_Switch' => true, + 'Stmt_While' => true, + 'Stmt_TryCatch' => true, + 'Stmt_Label' => true, + ); - protected $precedenceStack = array(19); - protected $precedenceStackPos = 0; + protected $precedenceStack; + protected $precedenceStackPos; + protected $noIndentToken; /** - * Pretty prints an array of statements. + * Pretty prints an array of nodes (statements). * - * @param array $nodes Array of statements + * @param array $nodes Array of nodes + * + * @return string Pretty printed nodes + */ + public function prettyPrint(array $nodes) { + $this->precedenceStack = array($this->precedenceStackPos = 0 => 19); + $this->noIndentToken = uniqid('_NO_INDENT_'); + + return str_replace("\n" . $this->noIndentToken, "\n", $this->pStmts($nodes, false)); + } + + /** + * Pretty prints an array of nodes (statements) and indents them optionally. + * + * @param array $nodes Array of nodes + * @param bool $indent Whether to indent the printed nodes * * @return string Pretty printed statements */ - public function pStmts(array $nodes) { - $return = ''; + protected function pStmts(array $nodes, $indent = true) { + $pNodes = array(); foreach ($nodes as $node) { - $return .= $this->p($node); - - if ( $node instanceof Node_Stmt_Func - || $node instanceof Node_Stmt_Interface - || $node instanceof Node_Stmt_Class - || $node instanceof Node_Stmt_ClassMethod - || $node instanceof Node_Stmt_For - || $node instanceof Node_Stmt_Foreach - || $node instanceof Node_Stmt_If - || $node instanceof Node_Stmt_Switch - || $node instanceof Node_Stmt_While - || $node instanceof Node_Stmt_TryCatch - || $node instanceof Node_Stmt_Label - ) { - $return .= "\n"; - } else { - $return .= ';' . "\n"; - } + $pNodes[] = $this->p($node) + . (isset($this->stmtsWithoutSemicolon[$node->getType()]) ? '' : ';'); + } + + if ($indent) { + return ' ' . preg_replace( + '~\n(?!$|' . $this->noIndentToken . ')~', + "\n" . ' ', + implode("\n", $pNodes) + ); + } else { + return implode("\n", $pNodes); } - return $return; } /** @@ -99,7 +121,7 @@ abstract class PrettyPrinterAbstract * * @return string Pretty printed node */ - public function p(NodeAbstract $node) { + protected function p(NodeAbstract $node) { $type = $node->getType(); if (isset($this->precedanceMap[$type])) { @@ -150,20 +172,13 @@ abstract class PrettyPrinterAbstract } /** - * Indents a string by four space characters. + * Signifies the pretty printer that a string shall not be indented. * - * @param string $string String to indent + * @param string $string Not to be indented string * - * @return string Indented string + * @return mixed String marked with $this->noIndentToken's. */ - protected function pIndent($string) { - $lines = explode("\n", $string); - foreach ($lines as &$line) { - if ('' !== $line) { - $line = ' ' . $line; - } - } - - return implode("\n", $lines); + protected function pSafe($string) { + return str_replace("\n", "\n" . $this->noIndentToken, $string); } } \ No newline at end of file diff --git a/test/testAgainstDirectory.php b/test/testAgainstDirectory.php index 69f25a4..c29196e 100644 --- a/test/testAgainstDirectory.php +++ b/test/testAgainstDirectory.php @@ -54,7 +54,7 @@ foreach (new RecursiveIteratorIterator( if (false !== $stmts) { ++$ppCount; $ppTime -= microtime(true); - $code = 'pStmts($stmts); + $code = 'prettyPrint($stmts); $ppTime += microtime(true); $ppStmts = $parser->parse(