diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/README.mkd b/README.mkd index bc4f2df..3c3ed5b 100644 --- a/README.mkd +++ b/README.mkd @@ -1,18 +1,21 @@ -What is PHP_Repl? +What is PHPRepl? ================= -PHP_Repl is a read-eval-print loop for PHP, written in PHP. It aims to be light, clean, modern, object-oriented, and to leverage the existing features of PHP wherever possible. +PHPRepl is a read-eval-print loop for PHP, written in PHP. It aims to be light, clean, modern, object-oriented, and to leverage the existing features of PHP wherever possible. Installation ============ - $ git clone git://github.com/ieure/php_repl.git - $ cd php_repl - $ pear package - $ pear install PHP_Repl*tgz +The library is available through Composer, so its easy to get it. Simply add this to your `composer.json` file: + + "require": { + "pmeth/php_repl": "dev-master" + } + +And run `composer install` Usage ===== -When you fire up PHP_Repl, you’re greeted with a prompt: +When you fire up PHPRepl, you’re greeted with a prompt: php> @@ -48,8 +51,8 @@ Uncaught Exceptions are caught and dumped: exception 'Exception' with message 'Test 123' in /Users/ieure/Projects/php_repl/PHP/Repl.php(143) : eval()'d code:1 Stack trace: #0 /Users/ieure/Projects/php_repl/PHP/Repl.php(143): eval() - #1 /Users/ieure/Projects/php_repl/PHP/Repl.php(62): PHP_Repl->run() - #2 /Users/ieure/Projects/php_repl/testrepl(20): PHP_Repl->__construct(Array) + #1 /Users/ieure/Projects/php_repl/PHP/Repl.php(62): PHPRepl->run() + #2 /Users/ieure/Projects/php_repl/testrepl(20): PHPRepl->__construct(Array) #3 {main} php> @@ -79,16 +82,16 @@ Documentation ------------- Doc blocks can be accessed with `,d`: - php> ,d PHP_Repl + php> ,d PHPRepl /** - * PHP_Repl + * PHPRepl * - * @package PHP_Repl + * @package PHPRepl * @author Ian Eure * @version @package_version@ */ '---' - php> ,d PHP_Repl::read + php> ,d PHPRepl::read /** * Read input * @@ -129,19 +132,19 @@ As mentioned, input is slightly altered before it is evaluated. Since this may p Reuse ===== -You can reuse PHP_Repl in your own projects. +You can reuse PHPRepl in your own projects. - require_once 'PHP/Repl.php'; + require_once 'PHPRepl.php'; function do_stuff() { if ($debug_condition) { - $repl = new PHP_Repl(); + $repl = new PHPRepl(); $repl->run(); } } -The REPL will have access to whatever scope your function does. If you invoke it from an object method, be aware that `$this` is always the PHP_Repl instance, not your object. +The REPL will have access to whatever scope your function does. If you invoke it from an object method, be aware that `$this` is always the PHPRepl instance, not your object. Passing Scope @@ -149,13 +152,13 @@ Passing Scope If you would like to pass additional variables into the REPL’s scope, you may include them in an array passed to run(). - require_once 'PHP/Repl.php'; + require_once 'PHPRepl.php'; class StuffDoer { function do_stuff() { if ($debug_condition) { - $repl = new PHP_Repl(); + $repl = new PHPRepl(); $repl->run(array('_caller' => $this)); } } @@ -164,7 +167,7 @@ If you would like to pass additional variables into the REPL’s scope, you may Configuration ============= -PHP_Repl stores its configuration in `$HOME/.phpreplrc`. A default file will be created for you if none exists. Any options you set while the REPL is running will be written out when you quit it. +PHPRepl stores its configuration in `$HOME/.phpreplrc`. A default file will be created for you if none exists. Any options you set while the REPL is running will be written out when you quit it. The following options are recognized: @@ -176,9 +179,9 @@ The following options are recognized: Dangers ======= - 1. The evaluated code is run in the same process as PHP_Repl, so if you evaluate something which causes a fatal error, PHP_Repl will terminate. Sorry. + 1. The evaluated code is run in the same process as PHPRepl, so if you evaluate something which causes a fatal error, PHPRepl will terminate. Sorry. 2. Output buffering is used to capture anything printed by eval’d code. If you have code which produces output over a long period of time, you may not see the results right away. Use `flush()`. - 3. Certain settings are enforced by PHP_Repl. You can’t change the values of: + 3. Certain settings are enforced by PHPRepl. You can’t change the values of: 1. `display_errors`. It’s always on. 2. `html_errors`. Always off. 3. `error_reporting`. Always `E_ALL | E_STRICT`. @@ -187,24 +190,6 @@ Dangers 6. The output buffer’s callback method. -Emacs Integration -================= -Because I really like Emacs, and enjoy the integration other languages (e.g. Common Lisp, Python, etc) have with it, I put together `phprepl.el`. It’s incomplete and may be buggy. - -It provides `run-php`, which will fire up the PHP REPL of your choice (not just PHP_Repl, it should work with any interactive PHP tool). It has the beginnings of a minor-mode to send regions, buffers, etc to the REPL for evaluation, but this isn’t finished yet. - -Installation ------------- -Drop `php-repl.el` somewhere in your `load-path` and `(require 'php-repl)`. - -Configuration -------------- -It’s customizable; `M-x customize-group RET phprepl RET`. Point `php-repl-program` at `PHP/Repl.php`. - -Usage ------ -`M-x run-php` will fire PHP_Repl up in a `*php*` buffer. Type and be happy. - Alternatives ============ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..3f509e7 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "pmeth/php_repl", + "description": "A command line REPL (Read-Evaluate-Print-Loop) for PHP written in PHP.", + "license": "BSD-2-Clause", + "authors": [ + { + "name": "Ian Eure", + "email": "ieure@php.net" + }, + { + "name": "Peter Meth", + "homepage": "http://softersoftware.com" + } + ], + + "require": { + "php": ">=5.3.0" + }, + + "autoload": { + "psr-0": { + "PHPRepl": "src/" + } + } +} diff --git a/data/php-repl.el b/data/php-repl.el deleted file mode 100644 index 08d6ad4..0000000 --- a/data/php-repl.el +++ /dev/null @@ -1,123 +0,0 @@ -;;; php-repl.el --- Interact with PHP - -;; Copyright © 2009 Ian Eure - -;; Maintainer: Ian Eure -;; Author: Ian Eure -;; Keywords: php repl -;; Created: 2009-03-27 -;; Modified: 2009-03-27 -;; X-URL: http://atomized.org - -;;; License - -;; This file is free software; you can redistribute it and/or -;; modify it under the terms of the GNU General Public License -;; as published by the Free Software Foundation; either version 3 -;; of the License, or (at your option) any later version. - -;; This file is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with this file; if not, write to the Free Software -;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -;; 02110-1301, USA. - -;;; Usage - -;; Put this file in your Emacs lisp path (eg. site-lisp) and add to -;; your .emacs file: -;; -;; (require 'php-repl) -;; -;; You can run an inferior PHP by invoking run-php. -;; - -(defconst php-repl-version-number "0.8.2" - "PHP_Repl Mode version number.") - -(require 'comint) - -(defgroup php-repl nil - "Major mode for interacting with an inferior PHP interpreter." - :prefix "ph-repl-" - :group 'php) - -(defcustom php-repl-program - "php-repl" - "Path to the PHP interpreter" - :type '(file)) - -(defcustom php-repl-program-arguments - '() - "Arguments for the PHP program." - :type '(repeat string)) - -(defcustom php-use-eval-php-mode nil - "Whether to enable php-eval-mode for PHP buffers." - :type 'boolean - :group 'php) - -(defvar inferior-php-buffer nil - "The buffer of the current inferior PHP processs") - -;;;###autoload -(defun run-php (&optional arg) - "Run an inferior PHP interpreter." - (interactive "p") - (let ((buf (cond ((buffer-live-p inferior-php-buffer) inferior-php-buffer) - (t (generate-new-buffer "*inferior-php*"))))) - (make-comint-in-buffer - "PHP" buf php-repl-program nil - (mapconcat 'identity php-repl-program-arguments " ")) - (setq inferior-php-buffer buf) - (display-buffer buf t) - ;; (pop-to-buffer buf t) - (inferior-php-mode))) - -(define-derived-mode inferior-php-mode comint-mode "Inferior PHP") -(defvar inferior-php-mode-abbrev-table - (make-abbrev-table)) -(derived-mode-merge-abbrev-tables php-mode-abbrev-table - inferior-php-mode-abbrev-table) -(derived-mode-set-abbrev-table 'inferior-php-mode) - -(defvar eval-php-mode-map - (let ((map (make-sparse-keymap))) - (define-key map "\C-c\C-r" 'php-send-region) - ;; (define-key map "\C-\M-x" 'php-send-defun) - (define-key map "\C-x\C-e" 'php-send-sexp) - ;; (define-key map "\C-x\C-:" 'php-send-expression) - map) - "Keymap for eval-php-mode.") - -(define-minor-mode eval-php-mode - "Minor mode for evaluating PHP code in an inferior process." nil "eval" - :keymap eval-php-mode-map - :group 'php-repl - :global nil) - -(add-hook 'php-mode-hook - '(lambda () - (when php-use-eval-php-mode (eval-php-mode t))) - -(defun php-send-region (start end) - "Send a region to the inferior PHP process." - (interactive "r") - (if (not (buffer-live-p inferior-php-buffer)) - (run-php)) - (save-excursion - (comint-send-region inferior-php-buffer start end) - (if (not (string-match "\n$" (buffer-substring start end))) - (comint-send-string sql-buffer "\n")) - (display-buffer inferior-php-buffer)))) - -(defun php-eval-sexp ()) -(defun php-eval-expression ()) -(defun php-eval-buffer ()) -(defun php-eval-line ()) - -(provide 'php-repl) diff --git a/examples/example.php b/examples/example.php new file mode 100644 index 0000000..e28809a --- /dev/null +++ b/examples/example.php @@ -0,0 +1,9 @@ +run(); diff --git a/examples/php-repl.sh b/examples/php-repl.sh new file mode 100755 index 0000000..7166205 --- /dev/null +++ b/examples/php-repl.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env php + + * @copyright 2009 Ian Eure. + * @version Release: $Id$ + * @filesource + */ + +// Add environment variable PHP_INCLUDE_PATH to include_path if it exists. +$includePath = getenv('PHP_INCLUDE_PATH'); +if ($includePath !== false) { + set_include_path(get_include_path().':'.getenv('PHP_INCLUDE_PATH')); +} + +require_once __DIR__ . '/../src/PHPRepl/PHPRepl.php'; +$__repl__ = new \PHPRepl\PHPRepl(); +$__repl__->run(); + +?> diff --git a/package.xml b/package.xml deleted file mode 100644 index 40146b2..0000000 --- a/package.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - PHP_Repl - pear.php.net - An interactive read-eval-print loop for PHP - PHP_Repl is a read-eval-print loop for PHP, written in PHP. It aims to be light, clean, modern, object-oriented, and to leverage the existing features of PHP wherever possible. - - Ian Eure - ieure - ieure@php.net - yes - - 2009-04-21 - - 0.8.6dev3 - 0.8.6 - - - beta - alpha - - New BSD License - - - Allow multiline input. - - Basic dissection and sugar. - - Save last-evaluated code in $__exp__. - - Start output buffering at the top of run(). - - - - - - - - - - - - - - - - - - - - - - - - - 5.2.0 - - - 1.7.0 - - - - - pcntl - - - - - diff --git a/scripts/php-repl b/scripts/php-repl deleted file mode 100755 index 909effc..0000000 --- a/scripts/php-repl +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env php - - * @copyright 2009 Ian Eure. - * @version Release: $Id$ - * @filesource - */ - -require_once 'PHP/Repl.php'; -$__repl__ = new PHP_Repl(); -$__repl__->run(); - -?> diff --git a/PHP/Repl.php b/src/PHPRepl/PHPRepl.php similarity index 54% rename from PHP/Repl.php rename to src/PHPRepl/PHPRepl.php index fe5faaf..f2a8ea3 100755 --- a/PHP/Repl.php +++ b/src/PHPRepl/PHPRepl.php @@ -1,5 +1,7 @@ * @version @package_version@ */ -class PHP_Repl +class PHPRepl { /** * Where we're reading input from * * @var resource */ - private $input; + protected $input; /** * The options for this instance * * @var array */ - private $options = array(); + protected $options = array(); + + protected $printers = array(); /** * The path to the configuration file * * @var string */ - private $rc_file; + protected $rc_file; /** @@ -56,30 +60,67 @@ public function __construct($options = array()) $this->rc_file = getenv('PHPREPLRC') ? getenv('PHPREPLRC') : getenv('HOME') . '/.phpreplrc'; - $defaults = $this->defaultOptions(); - $this->options = array_merge($defaults, $options); + $this->printers = $this->getDefaultPrinters(); + $defaults = $this->getDefaultOptions(); + $this->options = array_merge_recursive($defaults, $options); + + $this->readline_support = true; + if (!function_exists('readline') || getenv('TERM') == 'dumb') { + $this->readline_support = false; + } - if ($this->options['readline'] && - is_readable($this->options['readline_hist'])) { - readline_read_history($this->options['readline_hist']); + if ($this->readline_support && + is_readable($this->getOption('readline_hist'))) { + readline_read_history($this->getOption('readline_hist')); } } + public function getDefaultPrinters() + { + return array( + 'NULL' => null, + 'double' => 'var_dump', + 'float' => 'var_dump', + 'integer' => 'var_dump', + 'boolean' => 'var_dump', + 'string' => array('var_export',"\n"), + 'array' => 'print_r', + 'object' => 'print_r', + '_default_' => 'print_r', + ); + } + + public function getPrinters() + { + return $this->printers; + } + + public function setPrinter($type, $printer) + { + $this->printers[$type] = $printer; + } + + public function getPrinter($type) + { + if (!isset($this->printers[$type])) { + return $this->printers['_default_']; + } + + return $this->printers[$type]; + } + /** * Get default options * * @return array Defaults */ - private function defaultOptions() + public function getDefaultOptions() { - $defaults = array('prompt' => 'php> ', - 'readline' => true, - 'readline_hist' => getenv('HOME') . - '/.phprepl_history'); - - if (!function_exists('readline') || getenv('TERM') == 'dumb') { - $defaults['readline'] = false; - } + $defaults = array( + 'prompt' => 'php> ', + 'showtime' => false, + 'readline_hist' => getenv('HOME') . '/.phprepl_history', + ); if (is_readable($this->rc_file)) { $defaults = array_merge($defaults, parse_ini_file($this->rc_file)); @@ -87,6 +128,36 @@ private function defaultOptions() return $defaults; } + /** + * Set options + * + * @param array $options An array of options for use in the REPL + * + */ + public function setOptions(array $options) + { + $this->options = array_merge($this->options, $options); + } + + public function getOptions() + { + return $this->options; + } + + public function setOption($type, $option) + { + $this->options[$type] = $option; + } + + public function getOption($type) + { + if (!isset($this->options[$type])) { + return null; + } + + return $this->options[$type]; + } + /** * Destructor * @@ -95,11 +166,14 @@ private function defaultOptions() public function __destruct() { fclose($this->input); - if ($this->options['readline']) { - readline_write_history($this->options['readline_hist']); + if ($this->readline_support) { + readline_write_history($this->getOption('readline_hist')); } // Save config + /* turning off for now (maybe permanently) as support for nested + * arrays is broken + * $fp = fopen($this->rc_file, 'w'); if ($fp === false) { return; @@ -108,6 +182,7 @@ public function __destruct() fwrite($fp, "$k = \"$v\"\n"); } fclose($fp); + */ } /** @@ -119,26 +194,26 @@ public function __destruct() */ public function run(array $scope = array()) { + ob_implicit_flush(true); + error_reporting(E_ALL | E_STRICT); + ini_set('html_errors', 'Off'); + ini_set('display_errors', 'On'); extract($scope); ob_start(); while (true) { // inner loop is to escape from stacked output buffers - while ($__ob__ = ob_get_clean() ) { - echo $__ob__; + while ($__ob__ = ob_get_clean()) { + echo $this->obCleanup($__ob__); unset($__ob__); } try { if (((boolean) $__code__ = $this->read()) === false) { - break; + continue; } - ob_start(array($this, 'ob_cleanup')); - ob_implicit_flush(true); - error_reporting(E_ALL | E_STRICT); - ini_set('html_errors', 'Off'); - ini_set('display_errors', 'On'); + ob_start(array($this, 'obCleanup')); - $this->_print($_ = eval($this->cleanup($__code__))); + $this->output($_ = eval($this->cleanup($__code__))); } catch (Exception $e) { echo ($_ = $e) . "\n"; } @@ -157,6 +232,7 @@ private function read() $code = ''; $done = false; $lines = 0; + $stack = array(); static $shifted; if (!$shifted) { // throw away argv[0] @@ -164,10 +240,10 @@ private function read() $shifted = true; } do { - $prompt = $lines > 0 ? '> ' : $this->options['prompt']; + $prompt = $lines > 0 ? '> ' : ($this->getOption('showtime') ? date('G:i:s ') : '') . $this->getOption('prompt'); if (count($_SERVER['argv'])) { $line = array_shift($_SERVER['argv']); - } elseif ($this->options['readline']) { + } elseif ($this->readline_support) { $line = readline($prompt); } else { echo $prompt; @@ -179,21 +255,54 @@ private function read() return false; } + $done = true; $line = trim($line); // If the last char is a backslash, remove it and // accumulate more lines. if (substr($line, -1) == '\\') { - $line = substr($line, 0, strlen($line) - 1); - } else { - $done = true; + $line = trim(substr($line, 0, strlen($line) - 1)); + $done = false; + } + + // check for curleys and parens, keep accumulating lines. + $tokens = token_get_all(" 0) { + $last_t = array_pop($tokens); + if (is_array($last_t) && $last_t[0] == T_OPEN_TAG) { + // if the last token was an open tag, this is nothing. + } elseif ($stack[count($stack) - 1] === '{' && !in_array($last_t, array('{', '}', ';'))) { + // allow implied semicolons inside curlies + $line .= ';'; + } + $done = false; } $code .= $line; $lines++; } while (!$done); // Add the whole block to the readline history. - if ($this->options['readline']) { + if ($this->readline_support) { readline_add_history($code); + readline_write_history($this->getOption('readline_hist')); } return $code; } @@ -229,10 +338,11 @@ private function cleanup($input) $input = $last; } + $tokens = token_get_all("cleanup("\$this->{$sugar[$m]}($input)"); @@ -285,7 +395,7 @@ private function cleanup($input) * * @return string Cleaned up output */ - public function ob_cleanup($output) + public function obCleanup($output) { if (strlen($output) > 0 && substr($output, -1) != "\n") { $output .= "\n"; @@ -300,29 +410,23 @@ public function ob_cleanup($output) * * @return void */ - private function _print($out) + protected function output($out) { - $type = gettype($out); - switch ($type) { - case 'NULL': - break; - - case 'double': - case 'float': - case 'integer': - case 'boolean': - var_dump($out); - break; - - case 'string': - case 'array': - var_export($out); - echo "\n"; - break; - - default: - print_r($out); + $printer = $this->getPrinter(gettype($out)); + $extra = ''; + + if (!$printer) { + return; + } + + if (is_array($printer)) { + $extras = $printer; + $printer = array_shift($extras); + $extra = implode('', $extras); } + + call_user_func($printer, $out); + echo $extra; } /** @@ -335,36 +439,39 @@ private function _print($out) protected function getReflection($thing) { switch (true) { - case is_object($thing): - return new ReflectionObject($thing); - - case class_exists($thing, false): - return new ReflectionClass($thing); - - case function_exists($thing): - return new ReflectionFunction($thing); - - case strstr($thing, '::'): - list($class, $what) = explode('::', $thing); - $rc = new ReflectionClass($class); - - switch (true) { - case substr($what, -2) == '()': - $what = substr($what, 0, strlen($what) - 2); - case $rc->hasMethod($what): - return $rc->getMethod($what); - - case substr($what, 0, 1) == '$': - $what = substr($what, 1); - case $rc->hasProperty($what): - return $rc->getProperty($what); - - case $rc->hasConstant($what): - return $rc->getConstant($what); - } + case is_object($thing): + return new \ReflectionObject($thing); + + case class_exists($thing, false): + return new \ReflectionClass($thing); + + case function_exists($thing): + return new \ReflectionFunction($thing); + + case strstr($thing, '::'): + list($class, $what) = explode('::', $thing); + $rc = new \ReflectionClass($class); + + switch (true) { + case substr($what, -2) == '()': + $what = substr($what, 0, strlen($what) - 2); + // fallthrough + case $rc->hasMethod($what): + return $rc->getMethod($what); + + case substr($what, 0, 1) == '$': + $what = substr($what, 1); + // fallthrough + case $rc->hasProperty($what): + return $rc->getProperty($what); + + case $rc->hasConstant($what): + return $rc->getConstant($what); + } + // fallthrough - case is_string($thing): - return var_export($thing) . "\n"; + case is_string($thing): + return var_export($thing) . "\n"; } } @@ -395,8 +502,9 @@ protected function dir($thing) echo "\${$prop->getName()}\n"; } foreach ($rc->getMethods() as $meth) { - echo "\{$meth->getName()}()\n"; + echo "{$meth->getName()}()\n"; } + return "---"; } @@ -410,13 +518,14 @@ protected function dir($thing) protected function doc($thing) { if ($r = $this->getReflection($thing)) { - echo preg_replace('/^\s*\*/m', ' *', - $r->getDocComment()) . "\n"; + echo preg_replace( + '/^\s*\*/m', + ' *', + $r->getDocComment() + ) . "\n"; } else { echo "(no doc)\n"; } return "---"; } } - -?>