Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / vendor / psy / psysh / src / Command / TimeitCommand.php
index 1ac9281505c25ee7d3f96a450ad0877cd6519bc7..c59663131132c3d041991e2f1038ef5fba2c3c2e 100644 (file)
 
 namespace Psy\Command;
 
+use PhpParser\NodeTraverser;
+use PhpParser\PrettyPrinter\Standard as Printer;
+use Psy\Command\TimeitCommand\TimeitVisitor;
 use Psy\Input\CodeArgument;
-use Psy\Shell;
+use Psy\ParserFactory;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
@@ -25,6 +28,29 @@ class TimeitCommand extends Command
     const RESULT_MSG     = '<info>Command took %.6f seconds to complete.</info>';
     const AVG_RESULT_MSG = '<info>Command took %.6f seconds on average (%.6f median; %.6f total) to complete.</info>';
 
+    private static $start = null;
+    private static $times = [];
+
+    private $parser;
+    private $traverser;
+    private $printer;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function __construct($name = null)
+    {
+        $parserFactory = new ParserFactory();
+        $this->parser = $parserFactory->createParser();
+
+        $this->traverser = new NodeTraverser();
+        $this->traverser->addVisitor(new TimeitVisitor());
+
+        $this->printer = new Printer();
+
+        parent::__construct($name);
+    }
+
     /**
      * {@inheritdoc}
      */
@@ -57,23 +83,113 @@ HELP
         $num = $input->getOption('num') ?: 1;
         $shell = $this->getApplication();
 
-        $times = [];
+        $instrumentedCode = $this->instrumentCode($code);
+
+        self::$times = [];
+
         for ($i = 0; $i < $num; $i++) {
-            $start = microtime(true);
-            $_ = $shell->execute($code);
-            $times[] = microtime(true) - $start;
+            $_ = $shell->execute($instrumentedCode);
+            $this->ensureEndMarked();
         }
 
         $shell->writeReturnValue($_);
 
+        $times = self::$times;
+        self::$times = [];
+
         if ($num === 1) {
-            $output->writeln(sprintf(self::RESULT_MSG, $times[0]));
+            $output->writeln(\sprintf(self::RESULT_MSG, $times[0]));
         } else {
-            $total = array_sum($times);
-            rsort($times);
-            $median = $times[round($num / 2)];
+            $total = \array_sum($times);
+            \rsort($times);
+            $median = $times[\round($num / 2)];
+
+            $output->writeln(\sprintf(self::AVG_RESULT_MSG, $total / $num, $median, $total));
+        }
+    }
+
+    /**
+     * Internal method for marking the start of timeit execution.
+     *
+     * A static call to this method will be injected at the start of the timeit
+     * input code to instrument the call. We will use the saved start time to
+     * more accurately calculate time elapsed during execution.
+     */
+    public static function markStart()
+    {
+        self::$start = \microtime(true);
+    }
+
+    /**
+     * Internal method for marking the end of timeit execution.
+     *
+     * A static call to this method is injected by TimeitVisitor at the end
+     * of the timeit input code to instrument the call.
+     *
+     * Note that this accepts an optional $ret parameter, which is used to pass
+     * the return value of the last statement back out of timeit. This saves us
+     * a bunch of code rewriting shenanigans.
+     *
+     * @param mixed $ret
+     *
+     * @return mixed it just passes $ret right back
+     */
+    public static function markEnd($ret = null)
+    {
+        self::$times[] = \microtime(true) - self::$start;
+        self::$start = null;
+
+        return $ret;
+    }
+
+    /**
+     * Ensure that the end of code execution was marked.
+     *
+     * The end *should* be marked in the instrumented code, but just in case
+     * we'll add a fallback here.
+     */
+    private function ensureEndMarked()
+    {
+        if (self::$start !== null) {
+            self::markEnd();
+        }
+    }
+
+    /**
+     * Instrument code for timeit execution.
+     *
+     * This inserts `markStart` and `markEnd` calls to ensure that (reasonably)
+     * accurate times are recorded for just the code being executed.
+     *
+     * @param string $code
+     *
+     * @return string
+     */
+    private function instrumentCode($code)
+    {
+        return $this->printer->prettyPrint($this->traverser->traverse($this->parse($code)));
+    }
+
+    /**
+     * Lex and parse a string of code into statements.
+     *
+     * @param string $code
+     *
+     * @return array Statements
+     */
+    private function parse($code)
+    {
+        $code = '<?php ' . $code;
+
+        try {
+            return $this->parser->parse($code);
+        } catch (\PhpParser\Error $e) {
+            if (\strpos($e->getMessage(), 'unexpected EOF') === false) {
+                throw $e;
+            }
 
-            $output->writeln(sprintf(self::AVG_RESULT_MSG, $total / $num, $median, $total));
+            // If we got an unexpected EOF, let's try it again with a semicolon.
+            return $this->parser->parse($code . ';');
         }
     }
 }