public static function newFromTree(XHPASTTree $tree) { $obj = new XHPASTNodeList(); $obj->tree = $tree; $obj->list = array(0 => $tree->getRootNode()); $obj->ids = array(0 => 0); return $obj; }
/** * The actual testing method */ public function run() { /** @var XHPASTNode $root_node */ $root_node = $this->input->getRootNode(); /** @var AASTNodeList $classes */ $classes = $root_node->selectDescendantsOfType('n_CLASS_DECLARATION'); $class_symbols = array(); foreach ($classes as $class_node) { /** @var XHPASTNode $class_node */ $class_symbols[] = $class_node->getStringLiteralValue(); } $this->result = $class_symbols; $this->success = count($this->result) < 2; }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { $source = $request->getStr('source'); $future = xhpast_get_parser_future($source); $resolved = $future->resolve(); // This is just to let it throw exceptions if stuff is broken. $parse_tree = XHPASTTree::newFromDataAndResolvedExecFuture($source, $resolved); list($err, $stdout, $stderr) = $resolved; $storage_tree = new PhabricatorXHPASTViewParseTree(); $storage_tree->setInput($source); $storage_tree->setStdout($stdout); $storage_tree->setAuthorPHID($user->getPHID()); $storage_tree->save(); return id(new AphrontRedirectResponse())->setURI('/xhpast/view/' . $storage_tree->getID() . '/'); } $form = id(new AphrontFormView())->setUser($user)->appendChild(id(new AphrontFormTextAreaControl())->setLabel('Source')->setName('source')->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL))->appendChild(id(new AphrontFormSubmitControl())->setValue('Parse')); $panel = new AphrontPanelView(); $panel->setHeader('Generate XHP AST'); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); return $this->buildStandardPageResponse($panel, array('title' => 'XHPAST View')); }
public function willLintPaths(array $paths) { $futures = array(); foreach ($paths as $path) { if (array_key_exists($path, $this->trees)) { continue; } $futures[$path] = xhpast_get_parser_future($this->getData($path)); } foreach (Futures($futures)->limit(8) as $path => $future) { $this->willLintPath($path); $this->trees[$path] = null; try { $this->trees[$path] = XHPASTTree::newFromDataAndResolvedExecFuture($this->getData($path), $future->resolve()); $root = $this->trees[$path]->getRootNode(); $root->buildSelectCache(); $root->buildTokenCache(); } catch (XHPASTSyntaxErrorException $ex) { $this->raiseLintAtLine($ex->getErrorLine(), 1, self::LINT_PHP_SYNTAX_ERROR, 'This file contains a syntax error: ' . $ex->getMessage()); $this->stopAllLinters(); return; } catch (Exception $ex) { $this->raiseLintAtPath(self::LINT_UNABLE_TO_PARSE, 'XHPAST could not parse this file, probably because the AST is too ' . 'deep. Some lint issues may not have been detected. You may safely ' . 'ignore this warning.'); return; } } }
/** * The actual testing method */ public function run() { /** @var XHPASTNode $root_node */ $root_node = $this->input->getRootNode(); /** @var AASTNodeList $node */ $nodes = $root_node->selectDescendantsOfType('n_STATEMENT'); if ($nodes->count()) { $nodes->rewind(); /** @var XHPASTNode $node */ $node = $nodes->current(); $node_string = $node->getConcreteString(); if (strtolower(substr($node_string, 0, 5)) !== 'defin') { $this->result = $node_string; $this->success = false; } $this->result = $node_string; } }
public function processRequest() { $storage = $this->getStorageTree(); $input = $storage->getInput(); $stdout = $storage->getStdout(); $tree = XHPASTTree::newFromDataAndResolvedExecFuture($input, array(0, $stdout, '')); $tree = phutil_tag('ul', array(), $this->buildTree($tree->getRootNode())); return $this->buildXHPASTViewPanelResponse($tree); }
/** * Reads and parses test data from a specified file. * * This method reads and parses test data from a file. The file is expected * to have the following structure * * ``` * <?php * // PHP code goes here. * ~~~~~~~~~~ * { * // JSON dictionary containing expected results from testing method. * } * ``` * * @param string The path to the test file. * @return pair<XHPASTTree, map> The first element of the pair is the * `XHPASTTree` contained within the test file. * The second element of the pair is the * "expect" data. */ private function readTestData($file) { $contents = Filesystem::readFile($file); $contents = preg_split('/^~{10}$/m', $contents); if (count($contents) < 2) { throw new Exception(pht("Expected '%s' separating test case and results.", '~~~~~~~~~~')); } list($data, $expect) = $contents; $tree = XHPASTTree::newFromData($data); $expect = phutil_json_decode($expect); return array($tree, $expect); }
public static function evalStaticString($string) { $string = '<?php ' . rtrim($string, ';') . ';'; $tree = XHPASTTree::newFromData($string); $statements = $tree->getRootNode()->selectDescendantsOfType('n_STATEMENT'); if (count($statements) != 1) { throw new Exception("String does not parse into exactly one statement!"); } // Return the first one, trying to use reset() with iterators ends in tears. foreach ($statements as $statement) { return $statement->evalStatic(); } }
public function handleRequest(AphrontRequest $request) { $storage = $this->getStorageTree(); $input = $storage->getInput(); $stdout = $storage->getStdout(); $tree = XHPASTTree::newFromDataAndResolvedExecFuture($input, array(0, $stdout, '')); $tokens = array(); foreach ($tree->getRawTokenStream() as $id => $token) { $seq = $id; $name = $token->getTypeName(); $title = pht('Token %s: %s', $seq, $name); $tokens[] = phutil_tag('span', array('title' => $title, 'class' => 'token'), $token->getValue()); } return $this->buildXHPASTViewPanelResponse(phutil_implode_html('', $tokens)); }
public function processRequest() { $storage = $this->getStorageTree(); $input = $storage->getInput(); $stdout = $storage->getStdout(); $tree = XHPASTTree::newFromDataAndResolvedExecFuture($input, array(0, $stdout, '')); $tokens = array(); foreach ($tree->getRawTokenStream() as $id => $token) { $seq = $id; $name = $token->getTypeName(); $title = "Token {$seq}: {$name}"; $tokens[] = phutil_render_tag('span', array('title' => $title, 'class' => 'token'), phutil_escape_html($token->getValue())); } return $this->buildXHPASTViewPanelResponse(implode('', $tokens)); }
public function handleRequest(AphrontRequest $request) { $storage = $this->getStorageTree(); $input = $storage->getInput(); $err = $storage->getReturnCode(); $stdout = $storage->getStdout(); $stderr = $storage->getStderr(); try { $tree = XHPASTTree::newFromDataAndResolvedExecFuture($input, array($err, $stdout, $stderr)); } catch (XHPASTSyntaxErrorException $ex) { return $this->buildXHPASTViewPanelResponse($ex->getMessage()); } $tree = phutil_tag('ul', array(), $this->buildTree($tree->getRootNode())); return $this->buildXHPASTViewPanelResponse($tree); }
public function getXHPASTTreeForPath($path) { if (!array_key_exists($path, $this->trees)) { $this->trees[$path] = null; try { $this->trees[$path] = XHPASTTree::newFromDataAndResolvedExecFuture($this->getData($path), $this->futures[$path]->resolve()); $root = $this->trees[$path]->getRootNode(); $root->buildSelectCache(); $root->buildTokenCache(); } catch (XHPASTSyntaxErrorException $ex) { $this->raiseLintAtLine($ex->getErrorLine(), 1, self::LINT_PHP_SYNTAX_ERROR, 'This file contains a syntax error: ' . $ex->getMessage()); } catch (Exception $ex) { $this->raiseLintAtPath(self::LINT_UNABLE_TO_PARSE, $ex->getMessage()); } } return $this->trees[$path]; }
public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); if ($request->isFormPost()) { $source = $request->getStr('source'); $future = PhutilXHPASTBinary::getParserFuture($source); $resolved = $future->resolve(); // This is just to let it throw exceptions if stuff is broken. try { XHPASTTree::newFromDataAndResolvedExecFuture($source, $resolved); } catch (XHPASTSyntaxErrorException $ex) { // This is possibly expected. } list($err, $stdout, $stderr) = $resolved; $storage_tree = id(new PhabricatorXHPASTParseTree())->setInput($source)->setReturnCode($err)->setStdout($stdout)->setStderr($stderr)->setAuthorPHID($viewer->getPHID())->save(); return id(new AphrontRedirectResponse())->setURI('/xhpast/view/' . $storage_tree->getID() . '/'); } $form = id(new AphrontFormView())->setUser($viewer)->appendChild(id(new AphrontFormTextAreaControl())->setLabel(pht('Source'))->setName('source')->setValue("<?php\n\n")->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Parse'))); $form_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Generate XHP AST'))->setForm($form); return $this->buildApplicationPage($form_box, array('title' => pht('XHPAST View'))); }
private function extractFiles($root_path, array $files) { $hashes = array(); $futures = array(); foreach ($files as $file => $hash) { $full_path = $root_path . DIRECTORY_SEPARATOR . $file; $data = Filesystem::readFile($full_path); $futures[$full_path] = PhutilXHPASTBinary::getParserFuture($data); $hashes[$full_path] = $hash; } $bar = id(new PhutilConsoleProgressBar())->setTotal(count($futures)); $messages = array(); $results = array(); $futures = id(new FutureIterator($futures))->limit(8); foreach ($futures as $full_path => $future) { $bar->update(1); $hash = $hashes[$full_path]; try { $tree = XHPASTTree::newFromDataAndResolvedExecFuture(Filesystem::readFile($full_path), $future->resolve()); } catch (Exception $ex) { $messages[] = pht('WARNING: Failed to extract strings from file "%s": %s', $full_path, $ex->getMessage()); continue; } $root = $tree->getRootNode(); $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); foreach ($calls as $call) { $name = $call->getChildByIndex(0)->getConcreteString(); if ($name != 'pht') { continue; } $params = $call->getChildByIndex(1, 'n_CALL_PARAMETER_LIST'); $string_node = $params->getChildByIndex(0); $string_line = $string_node->getLineNumber(); try { $string_value = $string_node->evalStatic(); $results[$hash][] = array('string' => $string_value, 'file' => Filesystem::readablePath($full_path, $root_path), 'line' => $string_line); } catch (Exception $ex) { $messages[] = pht('WARNING: Failed to evaluate pht() call on line %d in "%s": %s', $call->getLineNumber(), $full_path, $ex->getMessage()); } } $tree->dispose(); } $bar->done(); foreach ($messages as $message) { echo tsprintf("**<bg:yellow> %s </bg>** %s\n", pht('WARNING'), $message); } return $results; }
if ($argc !== 1 || posix_isatty(STDIN)) { echo phutil_console_format("usage: find . -type f -name '*.php' | ./generate_php_symbols.php\n"); exit(1); } $input = file_get_contents('php://stdin'); $input = trim($input); $input = explode("\n", $input); $data = array(); $futures = array(); foreach ($input as $file) { $file = Filesystem::readablePath($file); $data[$file] = Filesystem::readFile($file); $futures[$file] = xhpast_get_parser_future($data[$file]); } foreach (Futures($futures)->limit(8) as $file => $future) { $tree = XHPASTTree::newFromDataAndResolvedExecFuture($data[$file], $future->resolve()); $root = $tree->getRootNode(); $scopes = array(); $functions = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION'); foreach ($functions as $function) { $name = $function->getChildByIndex(2); print_symbol($file, 'function', $name); } $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); foreach ($classes as $class) { $class_name = $class->getChildByIndex(1); print_symbol($file, 'class', $class_name); $scopes[] = array($class, $class_name); } $interfaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION'); foreach ($interfaces as $interface) {
/** * Get a path's tree from the responsible linter. * * @param string Path to retrieve tree for. * @return XHPASTTree|null Tree, or null if unparseable. * @task sharing */ protected final function getXHPASTTreeForPath($path) { // If we aren't the linter responsible for actually building the parse // trees, go get the tree from that linter. if ($this->getXHPASTLinter() !== $this) { return $this->getXHPASTLinter()->getXHPASTTreeForPath($path); } if (!array_key_exists($path, $this->trees)) { if (!array_key_exists($path, $this->futures)) { return; } $this->trees[$path] = null; try { $this->trees[$path] = XHPASTTree::newFromDataAndResolvedExecFuture($this->getData($path), $this->futures[$path]->resolve()); $root = $this->trees[$path]->getRootNode(); $root->buildSelectCache(); $root->buildTokenCache(); } catch (Exception $ex) { $this->exceptions[$path] = $ex; } } return $this->trees[$path]; }
private function assertEval($value, $string) { $this->assertEqual($value, XHPASTTree::newStatementFromString($string)->evalStatic(), $string); }
private function assertStringVariables($expected, $string) { $statement = XHPASTTree::newStatementFromString($string); $this->assertEqual($expected, $statement->getChildByIndex(0)->getStringVariables(), $string); }
private function assertEval($value, $string) { $this->assertEqual($value, XHPASTTree::evalStaticString($string), $string); }
private function applyXHPHighlight($source) { // We perform two passes here: one using the AST to find symbols we care // about -- particularly, class names and function names. These are used // in the crossreference stuff to link into Diffusion. After we've done our // AST pass, we do a followup pass on the token stream to catch all the // simple stuff like strings and comments. $scrub = false; if (strpos($source, '<?') === false) { $source = "<?php\n" . $source . "\n"; $scrub = true; } $tree = XHPASTTree::newFromData($source); $root = $tree->getRootNode(); $tokens = $root->getTokens(); $interesting_symbols = $this->findInterestingSymbols($root); $out = array(); foreach ($tokens as $key => $token) { $value = phutil_escape_html($token->getValue()); $class = null; $multi = false; $attrs = ''; if (isset($interesting_symbols[$key])) { $sym = $interesting_symbols[$key]; $class = $sym[0]; if (isset($sym['context'])) { $attrs = $attrs . ' data-symbol-context="' . $sym['context'] . '"'; } if (isset($sym['symbol'])) { $attrs = $attrs . ' data-symbol-name="' . $sym['symbol'] . '"'; } } else { switch ($token->getTypeName()) { case 'T_WHITESPACE': break; case 'T_DOC_COMMENT': $class = 'dc'; $multi = true; break; case 'T_COMMENT': $class = 'c'; $multi = true; break; case 'T_CONSTANT_ENCAPSED_STRING': case 'T_ENCAPSED_AND_WHITESPACE': case 'T_INLINE_HTML': $class = 's'; $multi = true; break; case 'T_VARIABLE': $class = 'nv'; break; case 'T_OPEN_TAG': case 'T_OPEN_TAG_WITH_ECHO': case 'T_CLOSE_TAG': $class = 'o'; break; case 'T_LNUMBER': case 'T_DNUMBER': $class = 'm'; break; case 'T_STRING': static $magic = array('true' => true, 'false' => true, 'null' => true); if (isset($magic[$value])) { $class = 'k'; break; } $class = 'nx'; break; default: $class = 'k'; break; } } if ($class) { $l = '<span class="' . $class . '"' . $attrs . '>'; $r = '</span>'; $value = $l . $value . $r; if ($multi) { // If the token may have multiple lines in it, make sure each // <span> crosses no more than one line so the lines can be put // in a table, etc., later. $value = str_replace("\n", $r . "\n" . $l, $value); } $out[] = $value; } else { $out[] = $value; } } if ($scrub) { array_shift($out); } return rtrim(implode('', $out)); }
private function applyXHPHighlight($result) { // We perform two passes here: one using the AST to find symbols we care // about -- particularly, class names and function names. These are used // in the crossreference stuff to link into Diffusion. After we've done our // AST pass, we do a followup pass on the token stream to catch all the // simple stuff like strings and comments. $tree = XHPASTTree::newFromDataAndResolvedExecFuture($this->source, $result); $root = $tree->getRootNode(); $tokens = $root->getTokens(); $interesting_symbols = $this->findInterestingSymbols($root); $out = array(); foreach ($tokens as $key => $token) { $value = $token->getValue(); $class = null; $multi = false; $attrs = array(); if (isset($interesting_symbols[$key])) { $sym = $interesting_symbols[$key]; $class = $sym[0]; $attrs['data-symbol-context'] = idx($sym, 'context'); $attrs['data-symbol-name'] = idx($sym, 'symbol'); } else { switch ($token->getTypeName()) { case 'T_WHITESPACE': break; case 'T_DOC_COMMENT': $class = 'dc'; $multi = true; break; case 'T_COMMENT': $class = 'c'; $multi = true; break; case 'T_CONSTANT_ENCAPSED_STRING': case 'T_ENCAPSED_AND_WHITESPACE': case 'T_INLINE_HTML': $class = 's'; $multi = true; break; case 'T_VARIABLE': $class = 'nv'; break; case 'T_OPEN_TAG': case 'T_OPEN_TAG_WITH_ECHO': case 'T_CLOSE_TAG': $class = 'o'; break; case 'T_LNUMBER': case 'T_DNUMBER': $class = 'm'; break; case 'T_STRING': static $magic = array('true' => true, 'false' => true, 'null' => true); if (isset($magic[strtolower($value)])) { $class = 'k'; break; } $class = 'nx'; break; default: $class = 'k'; break; } } if ($class) { $attrs['class'] = $class; if ($multi) { // If the token may have multiple lines in it, make sure each // <span> crosses no more than one line so the lines can be put // in a table, etc., later. $value = phutil_split_lines($value, $retain_endings = true); } else { $value = array($value); } foreach ($value as $val) { $out[] = phutil_tag('span', $attrs, $val); } } else { $out[] = $value; } } if ($this->scrub) { array_shift($out); } return phutil_implode_html('', $out); }
protected function executeAtomize($file_name, $file_data) { $future = PhutilXHPASTBinary::getParserFuture($file_data); $tree = XHPASTTree::newFromDataAndResolvedExecFuture($file_data, $future->resolve()); $atoms = array(); $root = $tree->getRootNode(); $func_decl = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION'); foreach ($func_decl as $func) { $name = $func->getChildByIndex(2); // Don't atomize closures if ($name->getTypeName() === 'n_EMPTY') { continue; } $atom = $this->newAtom(DivinerAtom::TYPE_FUNCTION)->setName($name->getConcreteString())->setLine($func->getLineNumber())->setFile($file_name); $this->findAtomDocblock($atom, $func); $this->parseParams($atom, $func); $this->parseReturnType($atom, $func); $atoms[] = $atom; } $class_types = array(DivinerAtom::TYPE_CLASS => 'n_CLASS_DECLARATION', DivinerAtom::TYPE_INTERFACE => 'n_INTERFACE_DECLARATION'); foreach ($class_types as $atom_type => $node_type) { $class_decls = $root->selectDescendantsOfType($node_type); foreach ($class_decls as $class) { $name = $class->getChildByIndex(1, 'n_CLASS_NAME'); $atom = $this->newAtom($atom_type)->setName($name->getConcreteString())->setFile($file_name)->setLine($class->getLineNumber()); // This parses `final` and `abstract`. $attributes = $class->getChildByIndex(0, 'n_CLASS_ATTRIBUTES'); foreach ($attributes->selectDescendantsOfType('n_STRING') as $attr) { $atom->setProperty($attr->getConcreteString(), true); } // If this exists, it is `n_EXTENDS_LIST`. $extends = $class->getChildByIndex(2); $extends_class = $extends->selectDescendantsOfType('n_CLASS_NAME'); foreach ($extends_class as $parent_class) { $atom->addExtends($this->newRef(DivinerAtom::TYPE_CLASS, $parent_class->getConcreteString())); } // If this exists, it is `n_IMPLEMENTS_LIST`. $implements = $class->getChildByIndex(3); $iface_names = $implements->selectDescendantsOfType('n_CLASS_NAME'); foreach ($iface_names as $iface_name) { $atom->addExtends($this->newRef(DivinerAtom::TYPE_INTERFACE, $iface_name->getConcreteString())); } $this->findAtomDocblock($atom, $class); $methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION'); foreach ($methods as $method) { $matom = $this->newAtom(DivinerAtom::TYPE_METHOD); $this->findAtomDocblock($matom, $method); $attribute_list = $method->getChildByIndex(0); $attributes = $attribute_list->selectDescendantsOfType('n_STRING'); if ($attributes) { foreach ($attributes as $attribute) { $attr = strtolower($attribute->getConcreteString()); switch ($attr) { case 'final': case 'abstract': case 'static': $matom->setProperty($attr, true); break; case 'public': case 'protected': case 'private': $matom->setProperty('access', $attr); break; } } } else { $matom->setProperty('access', 'public'); } $this->parseParams($matom, $method); $matom->setName($method->getChildByIndex(2)->getConcreteString()); $matom->setLine($method->getLineNumber()); $matom->setFile($file_name); $this->parseReturnType($matom, $method); $atom->addChild($matom); $atoms[] = $matom; } $atoms[] = $atom; } } return $atoms; }
as it's relatively stable and performance is currently awful (500ms+ for moderately large files). EOHELP ); $args->parseStandardArguments(); $args->parse(array(array('name' => 'all', 'help' => pht('Report all symbols, including built-ins and declared externals.')), array('name' => 'ugly', 'help' => pht('Do not prettify JSON output.')), array('name' => 'path', 'wildcard' => true, 'help' => pht('PHP Source file to analyze.')))); $paths = $args->getArg('path'); if (count($paths) !== 1) { throw new Exception(pht('Specify exactly one path!')); } $path = Filesystem::resolvePath(head($paths)); $show_all = $args->getArg('all'); $source_code = Filesystem::readFile($path); try { $tree = XHPASTTree::newFromData($source_code); } catch (XHPASTSyntaxErrorException $ex) { $result = array('error' => $ex->getMessage(), 'line' => $ex->getErrorLine(), 'file' => $path); $json = new PhutilJSON(); echo $json->encodeFormatted($result); exit(0); } $root = $tree->getRootNode(); $root->buildSelectCache(); // -( Unsupported Constructs )------------------------------------------------ $namespaces = $root->selectDescendantsOfType('n_NAMESPACE'); foreach ($namespaces as $namespace) { phutil_fail_on_unsupported_feature($namespace, $path, pht('namespaces')); } $uses = $root->selectDescendantsOfType('n_USE'); foreach ($namespaces as $namespace) {
private function applyXHPHighlight($result) { // We perform two passes here: one using the AST to find symbols we care // about -- particularly, class names and function names. These are used // in the crossreference stuff to link into Diffusion. After we've done our // AST pass, we do a followup pass on the token stream to catch all the // simple stuff like strings and comments. $tree = XHPASTTree::newFromDataAndResolvedExecFuture($this->source, $result); $root = $tree->getRootNode(); $tokens = $root->getTokens(); $interesting_symbols = $this->findInterestingSymbols($root); if ($this->scrub) { // If we're scrubbing, we prepended "<?php\n" to the text to force the // highlighter to treat it as PHP source. Now, we need to remove that. $ok = false; if (count($tokens) >= 2) { if ($tokens[0]->getTypeName() === 'T_OPEN_TAG') { if ($tokens[1]->getTypeName() === 'T_WHITESPACE') { $ok = true; } } } if (!$ok) { throw new Exception(pht('Expected T_OPEN_TAG, T_WHITESPACE tokens at head of results ' . 'for highlighting parse of PHP snippet.')); } // Remove the "<?php". unset($tokens[0]); $value = $tokens[1]->getValue(); if (strlen($value) < 1 || $value[0] != "\n") { throw new Exception(pht('Expected "\\n" at beginning of T_WHITESPACE token at head of ' . 'tokens for highlighting parse of PHP snippet.')); } $value = substr($value, 1); $tokens[1]->overwriteValue($value); } $out = array(); foreach ($tokens as $key => $token) { $value = $token->getValue(); $class = null; $multi = false; $attrs = array(); if (isset($interesting_symbols[$key])) { $sym = $interesting_symbols[$key]; $class = $sym[0]; $attrs['data-symbol-context'] = idx($sym, 'context'); $attrs['data-symbol-name'] = idx($sym, 'symbol'); } else { switch ($token->getTypeName()) { case 'T_WHITESPACE': break; case 'T_DOC_COMMENT': $class = 'dc'; $multi = true; break; case 'T_COMMENT': $class = 'c'; $multi = true; break; case 'T_CONSTANT_ENCAPSED_STRING': case 'T_ENCAPSED_AND_WHITESPACE': case 'T_INLINE_HTML': $class = 's'; $multi = true; break; case 'T_VARIABLE': $class = 'nv'; break; case 'T_OPEN_TAG': case 'T_OPEN_TAG_WITH_ECHO': case 'T_CLOSE_TAG': $class = 'o'; break; case 'T_LNUMBER': case 'T_DNUMBER': $class = 'm'; break; case 'T_STRING': static $magic = array('true' => true, 'false' => true, 'null' => true); if (isset($magic[strtolower($value)])) { $class = 'k'; break; } $class = 'nx'; break; default: $class = 'k'; break; } } if ($class) { $attrs['class'] = $class; if ($multi) { // If the token may have multiple lines in it, make sure each // <span> crosses no more than one line so the lines can be put // in a table, etc., later. $value = phutil_split_lines($value, $retain_endings = true); } else { $value = array($value); } foreach ($value as $val) { $out[] = phutil_tag('span', $attrs, $val); } } else { $out[] = $value; } } return phutil_implode_html('', $out); }