diff --git a/src/future/http/status/HTTPFutureHTTPResponseStatus.php b/src/future/http/status/HTTPFutureHTTPResponseStatus.php index 6031553..466030b 100644 --- a/src/future/http/status/HTTPFutureHTTPResponseStatus.php +++ b/src/future/http/status/HTTPFutureHTTPResponseStatus.php @@ -1,63 +1,62 @@ 512) { $excerpt = substr($body, 0, 512).'...'; } else { $excerpt = $body; } $content_type = BaseHTTPFuture::getHeader($headers, 'Content-Type'); $match = null; if (preg_match('/;\s*charset=([^;]+)/', $content_type, $match)) { $encoding = trim($match[1], "\"'"); try { $excerpt = phutil_utf8_convert($excerpt, 'UTF-8', $encoding); - } catch (Exception $ex) { - } + } catch (Exception $ex) {} } $this->excerpt = phutil_utf8ize($excerpt); $this->expect = $expect; parent::__construct($status_code); } protected function getErrorCodeType($code) { return 'HTTP'; } public function isError() { if ($this->expect === null) { return ($this->getStatusCode() < 200) || ($this->getStatusCode() > 299); } return !in_array($this->getStatusCode(), $this->expect, true); } public function isTimeout() { return false; } protected function getErrorCodeDescription($code) { static $map = array( 404 => 'Not Found', 500 => 'Internal Server Error', ); return idx($map, $code)."\n".$this->excerpt."\n"; } } diff --git a/src/markup/engine/PhutilRemarkupEngine.php b/src/markup/engine/PhutilRemarkupEngine.php index 8f8ebe0..7c551c9 100644 --- a/src/markup/engine/PhutilRemarkupEngine.php +++ b/src/markup/engine/PhutilRemarkupEngine.php @@ -1,298 +1,296 @@ config[$key] = $value; return $this; } public function getConfig($key, $default = null) { return idx($this->config, $key, $default); } public function setMode($mode) { $this->mode = $mode; return $this; } public function isTextMode() { return $this->mode & self::MODE_TEXT; } public function setBlockRules(array $rules) { assert_instances_of($rules, 'PhutilRemarkupBlockRule'); $rules = msort($rules, 'getPriority'); $this->blockRules = $rules; foreach ($this->blockRules as $rule) { $rule->setEngine($this); } $post_rules = array(); foreach ($this->blockRules as $block_rule) { foreach ($block_rule->getMarkupRules() as $rule) { $key = $rule->getPostprocessKey(); if ($key !== null) { $post_rules[$key] = $rule; } } } $this->postprocessRules = $post_rules; return $this; } public function getTextMetadata($key, $default = null) { if (isset($this->metadata[$key])) { return $this->metadata[$key]; } return idx($this->metadata, $key, $default); } public function setTextMetadata($key, $value) { $this->metadata[$key] = $value; return $this; } public function storeText($text) { if ($this->isTextMode()) { $text = phutil_safe_html($text); } return $this->storage->store($text); } public function overwriteStoredText($token, $new_text) { if ($this->isTextMode()) { $new_text = phutil_safe_html($new_text); } $this->storage->overwrite($token, $new_text); return $this; } public function markupText($text) { return $this->postprocessText($this->preprocessText($text)); } public function pushState($state) { if (empty($this->states[$state])) { $this->states[$state] = 0; } $this->states[$state]++; return $this; } public function popState($state) { if (empty($this->states[$state])) { throw new Exception("State '{$state}' pushed more than popped!"); } $this->states[$state]--; if (!$this->states[$state]) { unset($this->states[$state]); } return $this; } public function getState($state) { return !empty($this->states[$state]); } public function preprocessText($text) { $this->metadata = array(); $this->storage = new PhutilRemarkupBlockStorage(); $blocks = $this->splitTextIntoBlocks($text); $output = array(); foreach ($blocks as $block) { $output[] = $this->markupBlock($block); } $output = $this->flattenOutput($output); $map = $this->storage->getMap(); unset($this->storage); $metadata = $this->metadata; return array( 'output' => $output, 'storage' => $map, 'metadata' => $metadata, ); } private function splitTextIntoBlocks($text, $depth = 0) { // Apply basic block and paragraph normalization to the text. NOTE: We don't // strip trailing whitespace because it is semantic in some contexts, // notably inlined diffs that the author intends to show as a code block. $text = phutil_split_lines($text, true); $block_rules = $this->blockRules; $blocks = array(); $cursor = 0; $prev_block = array(); while (isset($text[$cursor])) { $starting_cursor = $cursor; foreach ($block_rules as $block_rule) { $num_lines = $block_rule->getMatchingLineCount($text, $cursor); if ($num_lines) { if ($blocks) { $prev_block = last($blocks); } $curr_block = array( 'start' => $cursor, 'num_lines' => $num_lines, 'rule' => $block_rule, 'is_empty' => self::isEmptyBlock($text, $cursor, $num_lines), 'children' => array(), ); if ($prev_block && self::shouldMergeBlocks($text, $prev_block, $curr_block)) { $blocks[last_key($blocks)]['num_lines'] += $curr_block['num_lines']; $blocks[last_key($blocks)]['is_empty'] = $blocks[last_key($blocks)]['is_empty'] && $curr_block['is_empty']; } else { $blocks[] = $curr_block; } $cursor += $num_lines; break; } } if ($starting_cursor === $cursor) { throw new Exception('Block in text did not match any block rule.'); } } foreach ($blocks as $key => $block) { $lines = array_slice($text, $block['start'], $block['num_lines']); $blocks[$key]['text'] = implode('', $lines); } // Stop splitting child blocks apart if we get too deep. This arrests // any blocks which have looping child rules, and stops the stack from // exploding if someone writes a hilarious comment with 5,000 levels of // quoted text. if ($depth < self::MAX_CHILD_DEPTH) { foreach ($blocks as $key => $block) { $rule = $block['rule']; if (!$rule->supportsChildBlocks()) { continue; } list($parent_text, $child_text) = $rule->extractChildText( $block['text']); $blocks[$key]['text'] = $parent_text; $blocks[$key]['children'] = $this->splitTextIntoBlocks( $child_text, $depth + 1); } } return $blocks; } private function markupBlock(array $block) { $children = array(); foreach ($block['children'] as $child) { $children[] = $this->markupBlock($child); } if ($children) { $children = $this->flattenOutput($children); } else { $children = null; } return $block['rule']->markupText($block['text'], $children); } private function flattenOutput(array $output) { if ($this->isTextMode()) { $output = implode("\n\n", $output)."\n"; } else { $output = phutil_implode_html("\n\n", $output); } return $output; } private static function shouldMergeBlocks($text, $prev_block, $curr_block) { $block_rules = ipull(array($prev_block, $curr_block), 'rule'); $default_rule = 'PhutilRemarkupDefaultBlockRule'; try { assert_instances_of($block_rules, $default_rule); // If the last block was empty keep merging if ($prev_block['is_empty']) { return true; } // If this line is blank keep merging if ($curr_block['is_empty']) { return true; } // If the current line and the last line have content, keep merging if (strlen(trim($text[$curr_block['start'] - 1]))) { if (strlen(trim($text[$curr_block['start']]))) { return true; } } - } catch (Exception $e) { - - } + } catch (Exception $e) {} return false; } private static function isEmptyBlock($text, $start, $num_lines) { for ($cursor = $start; $cursor < $start + $num_lines; $cursor++) { if (strlen(trim($text[$cursor]))) { return false; } } return true; } public function postprocessText(array $dict) { $this->metadata = idx($dict, 'metadata', array()); $this->storage = new PhutilRemarkupBlockStorage(); $this->storage->setMap(idx($dict, 'storage', array())); foreach ($this->blockRules as $block_rule) { $block_rule->postprocess(); } foreach ($this->postprocessRules as $rule) { $rule->didMarkupText(); } return $this->restoreText(idx($dict, 'output'), $this->isTextMode()); } public function restoreText($text) { return $this->storage->restore($text, $this->isTextMode()); } } diff --git a/src/object/__tests__/PhutilTestPhobject.php b/src/object/__tests__/PhutilTestPhobject.php index 40735e8..7bd5b81 100644 --- a/src/object/__tests__/PhutilTestPhobject.php +++ b/src/object/__tests__/PhutilTestPhobject.php @@ -1,5 +1,3 @@ * map * type|type * * A type may be marked as optional by suffixing it with "?" or prefixing it * with the word "optional": * * int? * optional int * * A type may have a human-readable comment in parentheses, at the end: * * int (must be even) * * For example, these are valid type specifications: * * int|string * map * list> * optional int * string (uppercase) * */ final class PhutilTypeSpec { private $type; private $subtypes = array(); private $optional; private $comment; - private function __construct() { - - } + private function __construct() {} public function getType() { return $this->type; } public function check($value, $name = null) { switch ($this->type) { case 'int': if (!is_int($value)) { throw new PhutilTypeCheckException($this, $value, $name); } break; case 'float': if (!is_float($value)) { throw new PhutilTypeCheckException($this, $value, $name); } break; case 'bool': if (!is_bool($value)) { throw new PhutilTypeCheckException($this, $value, $name); } break; case 'string': if (!is_string($value)) { throw new PhutilTypeCheckException($this, $value, $name); } break; case 'regex': $trap = new PhutilErrorTrap(); $ok = @preg_match($value, ''); $err = $trap->getErrorsAsString(); $trap->destroy(); if ($ok === false) { throw new PhutilTypeCheckException($this, $value, $name, $err); } break; case 'null': if (!is_null($value)) { throw new PhutilTypeCheckException($this, $value, $name); } break; case 'list': if (!is_array($value)) { throw new PhutilTypeCheckException($this, $value, $name); } if ($value && (array_keys($value) !== range(0, count($value) - 1))) { throw new PhutilTypeCheckException($this, $value, $name); } try { foreach ($value as $v) { $this->subtypes[0]->check($v); } } catch (PhutilTypeCheckException $ex) { throw new PhutilTypeCheckException($this, $value, $name); } break; case 'map': if (!is_array($value)) { throw new PhutilTypeCheckException($this, $value, $name); } try { foreach ($value as $k => $v) { $this->subtypes[0]->check($k); $this->subtypes[1]->check($v); } } catch (PhutilTypeCheckException $ex) { throw new PhutilTypeCheckException($this, $value, $name); } break; case 'or': foreach ($this->subtypes as $subtype) { try { $subtype->check($value); return; } catch (PhutilTypeCheckException $ex) { // Ignore. } } throw new PhutilTypeCheckException($this, $value, $name); case 'wild': return; default: if (class_exists($this->type, false)) { if ($value instanceof $this->type) { return; } } else if (interface_exists($this->type, false)) { if ($value instanceof $this->type) { return; } } throw new PhutilTypeCheckException($this, $value, $name); } } public static function checkMap(array $values, array $types) { $extra = array_diff_key($values, $types); if ($extra) { throw new PhutilTypeExtraParametersException($extra); } $missing = array(); foreach ($types as $key => $type) { $types[$key] = self::newFromString($type); if (!array_key_exists($key, $values)) { if (!$types[$key]->optional) { $missing[] = $key; } } } if ($missing) { throw new PhutilTypeMissingParametersException($missing); } foreach ($types as $key => $type) { if (array_key_exists($key, $values)) { $type->check($values[$key]); } } } public static function getCommonParentClass($class_a, $class_b) { $ancestors_a = array(); do { $ancestors_a[] = $class_a; } while ($class_a = get_parent_class($class_a)); $ancestors_b = array(); do { $ancestors_b[] = $class_b; } while ($class_b = get_parent_class($class_b)); return head(array_intersect($ancestors_a, $ancestors_b)); } public static function getTypeOf($value) { if (is_int($value)) { return 'int'; } else if (is_float($value)) { return 'float'; } else if (is_bool($value)) { return 'bool'; } else if (is_string($value)) { return 'string'; } else if (is_null($value)) { return 'null'; } else if (is_object($value)) { return get_class($value); } else if (is_array($value)) { $vtype = self::getTypeOfVector($value); if ($value && (array_keys($value) === range(0, count($value) - 1))) { return 'list<'.$vtype.'>'; } else { $ktype = self::getTypeOfVector(array_keys($value)); return "map<{$ktype}, {$vtype}>"; } } else { return 'wild'; } } private static function getTypeOfVector(array $vector) { if (!$vector) { return 'wild'; } $type = null; foreach ($vector as $value) { $vtype = self::getTypeOf($value); if ($type === null) { $type = $vtype; } else if ($type === $vtype) { continue; } else { $parent = self::getCommonParentClass($type, $vtype); if ($parent) { $type = $parent; } else { return 'wild'; } } } return $type; } public function toString() { $sub = array(); foreach ($this->subtypes as $subtype) { $sub[] = $subtype->toString(); } switch ($this->type) { case 'map': $string = 'map<'.$sub[0].', '.$sub[1].'>'; break; case 'list': $string = 'list<'.$sub[0].'>'; break; case 'or': $string = implode('|', $sub); break; default: $string = $this->type; break; } if ($this->optional) { $string = 'optional '.$string; } if ($this->comment) { $string .= ' ('.$this->comment.')'; } return $string; } public static function newFromString($string) { $lexer = self::getLexer(); $tokens = $lexer->getTokens($string); // Strip whitespace tokens. foreach ($tokens as $key => $token) { $type = $token[0]; if ($type == ' ') { unset($tokens[$key]); } } $tokens = array_values($tokens); $callback = array(__CLASS__, 'didReduceTokens'); return self::parseTokens($tokens, $callback); } public static function didReduceTokens($rule, $production, array $tokens) { switch ($rule) { case 'start': case 'some_type': case 'not_or_type': return $tokens[0]; case 'type': if ($production == 'yes') { $tokens[0]->optional = true; } return $tokens[0]; case 'basic_type': $obj = new PhutilTypeSpec(); $obj->type = $tokens[0][1]; return $obj; case 'or_type': $l = $tokens[0]; $r = $tokens[2]; if ($l->type == 'or') { if ($r->type == 'or') { foreach ($r->subtypes as $subtype) { $l->subtypes[] = $subtype; } } else { $l->subtypes[] = $r; } return $l; } else if ($r->type == 'or') { $r->subtypes[] = $l; return $r; } else { $obj = new PhutilTypeSpec(); $obj->type = 'or'; $obj->subtypes[] = $l; $obj->subtypes[] = $r; return $obj; } break; case 'map_type': $obj = new PhutilTypeSpec(); $obj->type = 'map'; $obj->subtypes[] = $tokens[2]; $obj->subtypes[] = $tokens[4]; return $obj; case 'list_type': $obj = new PhutilTypeSpec(); $obj->type = 'list'; $obj->subtypes[] = $tokens[2]; return $obj; case 'maybe_optional': if ($production == 'yes') { $tokens[1]->optional = true; return $tokens[1]; } else { return $tokens[0]; } break; case 'maybe_comment': if ($production == 'yes') { $tokens[0]->comment = $tokens[1]; } return $tokens[0]; case 'comment': return $tokens[1]; case 'comment_text': $result = ''; foreach ($tokens as $token) { if (is_array($token)) { $result .= $token[1]; } else { $result .= $token; } } return $result; default: throw new Exception("Unhandled parser rule '{$rule}'!"); } } private static function getLexer() { static $lexer; if (!$lexer) { $lexer = new PhutilTypeLexer(); } return $lexer; } private static function parseTokens(array $tokens, $callback) { // NOTE: This is automatically generated by the script // `support/parser/generate-type-parser.php`. return PhutilParserGenerator::parseTokensWithTables( array( 0 => array( 'opt' => array( 0 => 'S', 1 => 3, ), 'k' => array( 0 => 'S', 1 => 20, ), 'map' => array( 0 => 'S', 1 => 21, ), 'list' => array( 0 => 'S', 1 => 71, ), ), 1 => array( '(end-of-file)' => array( 0 => 'A', ), ), 2 => array( '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'start', 1 => 0, 2 => 1, ), ), ), 3 => array( 'k' => array( 0 => 'S', 1 => 20, ), 'map' => array( 0 => 'S', 1 => 21, ), 'list' => array( 0 => 'S', 1 => 71, ), ), 4 => array( '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'maybe_optional', 1 => 'yes', 2 => 2, ), ), ), 5 => array( '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'maybe_comment', 1 => 'no', 2 => 1, ), ), '(' => array( 0 => 'S', 1 => 7, ), ), 6 => array( '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'maybe_comment', 1 => 'yes', 2 => 2, ), ), ), 7 => array( 'cm' => array( 0 => 'S', 1 => 11, ), ), 8 => array( ')' => array( 0 => 'S', 1 => 9, ), 'cm' => array( 0 => 'S', 1 => 10, ), ), 9 => array( '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'comment', 1 => 0, 2 => 3, ), ), ), 10 => array( ')' => array( 0 => 'R', 1 => array( 0 => 'comment_text', 1 => 0, 2 => 2, ), ), 'cm' => array( 0 => 'R', 1 => array( 0 => 'comment_text', 1 => 0, 2 => 2, ), ), ), 11 => array( ')' => array( 0 => 'R', 1 => array( 0 => 'comment_text', 1 => 1, 2 => 1, ), ), 'cm' => array( 0 => 'R', 1 => array( 0 => 'comment_text', 1 => 1, 2 => 1, ), ), ), 12 => array( '(' => array( 0 => 'R', 1 => array( 0 => 'type', 1 => 'no', 2 => 1, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'type', 1 => 'no', 2 => 1, ), ), '?' => array( 0 => 'S', 1 => 13, ), ), 13 => array( '(' => array( 0 => 'R', 1 => array( 0 => 'type', 1 => 'yes', 2 => 2, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'type', 1 => 'yes', 2 => 2, ), ), ), 14 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 0, 2 => 1, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 0, 2 => 1, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 0, 2 => 1, ), ), '|' => array( 0 => 'S', 1 => 15, ), ), 15 => array( 'k' => array( 0 => 'S', 1 => 20, ), 'map' => array( 0 => 'S', 1 => 21, ), 'list' => array( 0 => 'S', 1 => 71, ), ), 16 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), ), 17 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), ), 18 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), ), 19 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), ), 20 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), ), 21 => array( '<' => array( 0 => 'S', 1 => 22, ), ), 22 => array( 'k' => array( 0 => 'S', 1 => 57, ), 'map' => array( 0 => 'S', 1 => 58, ), 'list' => array( 0 => 'S', 1 => 67, ), ), 23 => array( ',' => array( 0 => 'S', 1 => 24, ), ), 24 => array( 'k' => array( 0 => 'S', 1 => 35, ), 'map' => array( 0 => 'S', 1 => 36, ), 'list' => array( 0 => 'S', 1 => 45, ), ), 25 => array( '>' => array( 0 => 'S', 1 => 26, ), ), 26 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), ), 27 => array( '>' => array( 0 => 'R', 1 => array( 0 => 'type', 1 => 'no', 2 => 1, ), ), '?' => array( 0 => 'S', 1 => 28, ), ), 28 => array( '>' => array( 0 => 'R', 1 => array( 0 => 'type', 1 => 'yes', 2 => 2, ), ), ), 29 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 0, 2 => 1, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 0, 2 => 1, ), ), '|' => array( 0 => 'S', 1 => 30, ), ), 30 => array( 'k' => array( 0 => 'S', 1 => 35, ), 'map' => array( 0 => 'S', 1 => 36, ), 'list' => array( 0 => 'S', 1 => 45, ), ), 31 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), ), 32 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), ), 33 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), ), 34 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), ), 35 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), ), 36 => array( '<' => array( 0 => 'S', 1 => 37, ), ), 37 => array( 'k' => array( 0 => 'S', 1 => 57, ), 'map' => array( 0 => 'S', 1 => 58, ), 'list' => array( 0 => 'S', 1 => 67, ), ), 38 => array( ',' => array( 0 => 'S', 1 => 39, ), ), 39 => array( 'k' => array( 0 => 'S', 1 => 35, ), 'map' => array( 0 => 'S', 1 => 36, ), 'list' => array( 0 => 'S', 1 => 45, ), ), 40 => array( '>' => array( 0 => 'S', 1 => 41, ), ), 41 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), ), 42 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 1, 2 => 1, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 1, 2 => 1, ), ), '|' => array( 0 => 'S', 1 => 43, ), ), 43 => array( 'k' => array( 0 => 'S', 1 => 35, ), 'map' => array( 0 => 'S', 1 => 36, ), 'list' => array( 0 => 'S', 1 => 45, ), ), 44 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), ), 45 => array( '<' => array( 0 => 'S', 1 => 46, ), ), 46 => array( 'k' => array( 0 => 'S', 1 => 35, ), 'map' => array( 0 => 'S', 1 => 36, ), 'list' => array( 0 => 'S', 1 => 45, ), ), 47 => array( '>' => array( 0 => 'S', 1 => 48, ), ), 48 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), '>' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), ), 49 => array( ',' => array( 0 => 'R', 1 => array( 0 => 'type', 1 => 'no', 2 => 1, ), ), '?' => array( 0 => 'S', 1 => 50, ), ), 50 => array( ',' => array( 0 => 'R', 1 => array( 0 => 'type', 1 => 'yes', 2 => 2, ), ), ), 51 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 0, 2 => 1, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 0, 2 => 1, ), ), '|' => array( 0 => 'S', 1 => 52, ), ), 52 => array( 'k' => array( 0 => 'S', 1 => 57, ), 'map' => array( 0 => 'S', 1 => 58, ), 'list' => array( 0 => 'S', 1 => 67, ), ), 53 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 0, 2 => 3, ), ), ), 54 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 0, 2 => 1, ), ), ), 55 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 1, 2 => 1, ), ), ), 56 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'not_or_type', 1 => 2, 2 => 1, ), ), ), 57 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'basic_type', 1 => 0, 2 => 1, ), ), ), 58 => array( '<' => array( 0 => 'S', 1 => 59, ), ), 59 => array( 'k' => array( 0 => 'S', 1 => 57, ), 'map' => array( 0 => 'S', 1 => 58, ), 'list' => array( 0 => 'S', 1 => 67, ), ), 60 => array( ',' => array( 0 => 'S', 1 => 61, ), ), 61 => array( 'k' => array( 0 => 'S', 1 => 35, ), 'map' => array( 0 => 'S', 1 => 36, ), 'list' => array( 0 => 'S', 1 => 45, ), ), 62 => array( '>' => array( 0 => 'S', 1 => 63, ), ), 63 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'map_type', 1 => 0, 2 => 6, ), ), ), 64 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 1, 2 => 1, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 1, 2 => 1, ), ), '|' => array( 0 => 'S', 1 => 65, ), ), 65 => array( 'k' => array( 0 => 'S', 1 => 57, ), 'map' => array( 0 => 'S', 1 => 58, ), 'list' => array( 0 => 'S', 1 => 67, ), ), 66 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), ), 67 => array( '<' => array( 0 => 'S', 1 => 68, ), ), 68 => array( 'k' => array( 0 => 'S', 1 => 35, ), 'map' => array( 0 => 'S', 1 => 36, ), 'list' => array( 0 => 'S', 1 => 45, ), ), 69 => array( '>' => array( 0 => 'S', 1 => 70, ), ), 70 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), ',' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), ), 71 => array( '<' => array( 0 => 'S', 1 => 72, ), ), 72 => array( 'k' => array( 0 => 'S', 1 => 35, ), 'map' => array( 0 => 'S', 1 => 36, ), 'list' => array( 0 => 'S', 1 => 45, ), ), 73 => array( '>' => array( 0 => 'S', 1 => 74, ), ), 74 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'list_type', 1 => 0, 2 => 4, ), ), ), 75 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 1, 2 => 1, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 1, 2 => 1, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'some_type', 1 => 1, 2 => 1, ), ), '|' => array( 0 => 'S', 1 => 76, ), ), 76 => array( 'k' => array( 0 => 'S', 1 => 20, ), 'map' => array( 0 => 'S', 1 => 21, ), 'list' => array( 0 => 'S', 1 => 71, ), ), 77 => array( '?' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), '(' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), '|' => array( 0 => 'R', 1 => array( 0 => 'or_type', 1 => 1, 2 => 3, ), ), ), 78 => array( '(end-of-file)' => array( 0 => 'R', 1 => array( 0 => 'maybe_optional', 1 => 'no', 2 => 1, ), ), ), ), array( 0 => array( 'start' => 1, 'maybe_optional' => 2, 'maybe_comment' => 78, 'type' => 5, 'some_type' => 12, 'or_type' => 14, 'not_or_type' => 75, 'basic_type' => 17, 'map_type' => 18, 'list_type' => 19, ), 3 => array( 'maybe_comment' => 4, 'type' => 5, 'some_type' => 12, 'or_type' => 14, 'not_or_type' => 75, 'basic_type' => 17, 'map_type' => 18, 'list_type' => 19, ), 5 => array( 'comment' => 6, ), 7 => array( 'comment_text' => 8, ), 15 => array( 'not_or_type' => 16, 'basic_type' => 17, 'map_type' => 18, 'list_type' => 19, ), 22 => array( 'type' => 23, 'some_type' => 49, 'or_type' => 51, 'not_or_type' => 64, 'basic_type' => 54, 'map_type' => 55, 'list_type' => 56, ), 24 => array( 'type' => 25, 'some_type' => 27, 'or_type' => 29, 'not_or_type' => 42, 'basic_type' => 32, 'map_type' => 33, 'list_type' => 34, ), 30 => array( 'not_or_type' => 31, 'basic_type' => 32, 'map_type' => 33, 'list_type' => 34, ), 37 => array( 'type' => 38, 'some_type' => 49, 'or_type' => 51, 'not_or_type' => 64, 'basic_type' => 54, 'map_type' => 55, 'list_type' => 56, ), 39 => array( 'type' => 40, 'some_type' => 27, 'or_type' => 29, 'not_or_type' => 42, 'basic_type' => 32, 'map_type' => 33, 'list_type' => 34, ), 43 => array( 'not_or_type' => 44, 'basic_type' => 32, 'map_type' => 33, 'list_type' => 34, ), 46 => array( 'type' => 47, 'some_type' => 27, 'or_type' => 29, 'not_or_type' => 42, 'basic_type' => 32, 'map_type' => 33, 'list_type' => 34, ), 52 => array( 'not_or_type' => 53, 'basic_type' => 54, 'map_type' => 55, 'list_type' => 56, ), 59 => array( 'type' => 60, 'some_type' => 49, 'or_type' => 51, 'not_or_type' => 64, 'basic_type' => 54, 'map_type' => 55, 'list_type' => 56, ), 61 => array( 'type' => 62, 'some_type' => 27, 'or_type' => 29, 'not_or_type' => 42, 'basic_type' => 32, 'map_type' => 33, 'list_type' => 34, ), 65 => array( 'not_or_type' => 66, 'basic_type' => 54, 'map_type' => 55, 'list_type' => 56, ), 68 => array( 'type' => 69, 'some_type' => 27, 'or_type' => 29, 'not_or_type' => 42, 'basic_type' => 32, 'map_type' => 33, 'list_type' => 34, ), 72 => array( 'type' => 73, 'some_type' => 27, 'or_type' => 29, 'not_or_type' => 42, 'basic_type' => 32, 'map_type' => 33, 'list_type' => 34, ), 76 => array( 'not_or_type' => 77, 'basic_type' => 17, 'map_type' => 18, 'list_type' => 19, ), ), '(end-of-file)', $tokens, $callback); } } diff --git a/src/parser/aast/api/AASTNodeList.php b/src/parser/aast/api/AASTNodeList.php index 25e8240..d8e6ade 100644 --- a/src/parser/aast/api/AASTNodeList.php +++ b/src/parser/aast/api/AASTNodeList.php @@ -1,137 +1,135 @@ ids); } public function current() { return $this->list[$this->key()]; } public function rewind() { $this->pos = 0; } public function valid() { return $this->pos < count($this->ids); } public function next() { $this->pos++; } public function key() { return $this->ids[$this->pos]; } public static function newFromTreeAndNodes(AASTTree $tree, array $nodes) { assert_instances_of($nodes, 'AASTNode'); $obj = new AASTNodeList(); $obj->tree = $tree; $obj->list = $nodes; $obj->ids = array_keys($nodes); return $obj; } public static function newFromTree(AASTTree $tree) { $obj = new AASTNodeList(); $obj->tree = $tree; $obj->list = array(0 => $tree->getRootNode()); $obj->ids = array(0 => 0); return $obj; } - protected function __construct() { - - } + protected function __construct() {} public function getDescription() { if (empty($this->list)) { return 'an empty node list'; } $desc = array(); $desc[] = 'a list of '.count($this->list).' nodes:'; foreach ($this->list as $node) { $desc[] = ' '.$node->getDescription().';'; } return implode("\n", $desc); } protected function newList(array $nodes) { return AASTNodeList::newFromTreeAndNodes( $this->tree, $nodes); } public function selectDescendantsOfType($type_name) { $results = array(); foreach ($this->list as $id => $node) { $results += $node->selectDescendantsOfType($type_name)->getRawNodes(); } return $this->newList($results); } public function selectDescendantsOfTypes(array $type_names) { $results = array(); foreach ($type_names as $type_name) { foreach ($this->list as $id => $node) { $results += $node->selectDescendantsOfType($type_name)->getRawNodes(); } } return $this->newList($results); } public function getChildrenByIndex($index) { $results = array(); foreach ($this->list as $id => $node) { $child = $node->getChildByIndex($index); $results[$child->getID()] = $child; } return $this->newList($results); } public function add(AASTNodeList $list) { foreach ($list->list as $id => $node) { $this->list[$id] = $node; } $this->ids = array_keys($this->list); return $this; } protected function executeSelectDescendantsOfType($node, $type) { $results = array(); foreach ($node->getChildren() as $id => $child) { if ($child->getTypeID() == $type) { $results[$id] = $child; } else { $results += $this->executeSelectDescendantsOfType($child, $type); } } return $results; } public function getTokens() { $tokens = array(); foreach ($this->list as $node) { $tokens += $node->getTokens(); } return $tokens; } public function getRawNodes() { return $this->list; } } diff --git a/src/serviceprofiler/PhutilServiceProfiler.php b/src/serviceprofiler/PhutilServiceProfiler.php index 815ed11..4429488 100644 --- a/src/serviceprofiler/PhutilServiceProfiler.php +++ b/src/serviceprofiler/PhutilServiceProfiler.php @@ -1,162 +1,161 @@ discardMode = true; } public static function getInstance() { if (empty(self::$instance)) { self::$instance = new PhutilServiceProfiler(); } return self::$instance; } public function beginServiceCall(array $data) { $data['begin'] = microtime(true); $id = $this->logSize++; $this->events[$id] = $data; foreach ($this->listeners as $listener) { call_user_func($listener, 'begin', $id, $data); } return $id; } public function endServiceCall($call_id, array $data) { $data = ($this->events[$call_id] + $data); $data['end'] = microtime(true); $data['duration'] = ($data['end'] - $data['begin']); $this->events[$call_id] = $data; foreach ($this->listeners as $listener) { call_user_func($listener, 'end', $call_id, $data); } if ($this->discardMode) { unset($this->events[$call_id]); } } public function getServiceCallLog() { return $this->events; } public function addListener($callback) { $this->listeners[] = $callback; } public static function installEchoListener() { $instance = PhutilServiceProfiler::getInstance(); $instance->addListener(array('PhutilServiceProfiler', 'echoListener')); } public static function echoListener($type, $id, $data) { $is_begin = false; $is_end = false; switch ($type) { case 'begin': $is_begin = true; $mark = '>>>'; break; case 'end': $is_end = true; $mark = '<<<'; break; default: $mark = null; break; } $type = idx($data, 'type', 'mystery'); $desc = null; if ($is_begin) { switch ($type) { case 'connect': $desc = $data['database']; break; case 'query': $desc = substr($data['query'], 0, 512); break; case 'multi-query': $desc = array(); foreach ($data['queries'] as $query) { $desc[] = substr($query, 0, 256); } $desc = implode('; ', $desc); break; case 'exec': $desc = '$ '.$data['command']; break; case 'conduit': if (isset($data['size'])) { $desc = $data['method'].'() '; } else { $desc = $data['method'].'()'; } break; case 'http': $desc = phutil_censor_credentials($data['uri']); break; case 'lint': $desc = $data['linter']; if (isset($data['paths'])) { $desc .= ' '; } break; case 'lock': $desc = $data['name']; break; case 'event': $desc = $data['kind'].' '; break; case 'ldap': $call = idx($data, 'call', '?'); $params = array(); switch ($call) { case 'connect': $params[] = $data['host'].':'.$data['port']; break; case 'start-tls': break; case 'bind': $params[] = $data['user']; break; case 'search': $params[] = $data['dn']; $params[] = $data['query']; break; default: $params[] = '?'; break; } $desc = "{$call} (".implode(', ', $params).")"; break; } } else if ($is_end) { $desc = number_format((int)(1000000 * $data['duration'])).' us'; } $console = PhutilConsole::getConsole(); $console->writeLog( "%s [%s] <%s> %s\n", $mark, $id, $type, $desc); } }