input()) { try { ## parse the input ## - check for functions, classes ## - check for multi-line if ($shell->parse() == 0) { $retval = eval($shell->getCode()); if (isset($retval)) { var_dump($retval); } ## reset the cmdline $shell->resetCode(); } } catch(Exception $e) { ## oops, error print $e->getMessage(); $shell->resetCode(); } } Usage: $ php php-shell.php >> function foo($v) { .. return $v ? 1 : 0; .. } >> foo() int(1) */ function readline_complete($str, $pos) { $in = readline_info('line_buffer'); /** * parse the lb backwards to see if we have a * - constant * - function * - variable */ $m = array(); if (preg_match('#\$([A-Za-z0-9_]+)->#', $in, $a)) { /* check for $o->... */ $name = $a[1]; if (isset($GLOBALS[$name])) { $c = get_class_methods($GLOBALS[$name]); foreach ($c as $v) { $m[] = $v.'('; } $c = get_class_vars(get_class($GLOBALS[$name])); foreach ($c as $v) { $m[] = $v; } return $m; } } else if (preg_match('#([A-Za-z0-9_]+)::#', $in, $a)) { /* check for :: */ $name = $a[1]; if (class_exists($name, false)) { $c = get_class_methods($name); foreach ($c as $v) { $m[] = sprintf('%s::%s(', $name, $v); } return $m; } } else if (preg_match('#\$([a-zA-Z]?[a-zA-Z0-9_]*)$#', $in)) { $m = array_keys($GLOBALS); return $m; } else if (preg_match('#new #', $in)) { $c = get_declared_classes(); foreach ($c as $v) { $m[] = $v.'('; } return $m; } $f = get_defined_functions(); foreach ($f['internal'] as $v) { $m[] = $v.'('; } foreach ($f['user'] as $v) { $m[] = $v.'('; } $c = get_declared_classes(); foreach ($c as $v) { $m[] = $v.'::'; } $c = get_defined_constants(); foreach ($c as $k => $v) { $m[] = $k; } $m[] = 'foreach ('; # printf("%s ... %s\n", $str, $pos); return $m; } class PHP_Shell { protected $code; public $retval; protected $verbose; protected $have_readline; public function __construct() { $this->code = ''; unset($this->retval); $this->vars = array(); $this->stdin = fopen("php://stdin", "r") or die(); $this->have_readline = function_exists('readline'); if ($this->have_readline) { readline_completion_function('readline_complete'); } } /** parse the PHP code to - fetch fatal errors before they come up - know about where we have to wait for closing braces FIXME: - the parsing should be stack-based with look-back instead of using flags */ public function parse() { $braces = array(); $first_token = 0; $last_token = 0; ## remove empty lines $this->code = trim($this->code); if ($this->code == '') return 1; $t = token_get_all('code.' ?>'); $need_block = 0; $need_comma = 1; $need_return = 1; $eval = ''; $last_string = ''; /* this should be stack based */ $is_function = 1; $is_class = 0; $is_extends = 0; foreach ($t as $token) { if (is_array($token)) { $ignore = 0; switch($token[0]) { case T_WHITESPACE: case T_OPEN_TAG: case T_CLOSE_TAG: $ignore = 1; break; case T_VARIABLE: # the var-name is $foo, but we only need the 'foo' if (!isset($this->vars[ltrim($token[1], '$')])) { $this->vars[ltrim($token[1], '$')] = null; } break; case T_STRING: $last_string = $token[1]; break; case T_NEW: $is_function = 0; $is_class = 1; break; case T_FUNCTION: $is_function = 0; $need_return = 0; break; case T_DOUBLE_COLON: /* last_string is a classname */ if (!class_exists($last_string)) { throw new Exception(sprintf("Class %s doesn't exist", $last_string)); } $is_function = 0; break; case T_OBJECT_OPERATOR: $is_function = 0; break; case T_CLASS: $is_class = 1; $need_return = 0; break; case T_FOREACH: $need_return = 0; break; case T_EXTENDS: if (class_exists($last_string, false)) { throw new Exception(sprintf("Class %s already exists", $last_string)); } $is_class = 0; $is_extends = 1; break; case T_PRINT: case T_ECHO: case T_RETURN: $need_return = 0; break; case T_IF: $is_function = 0; $need_return = 0; break; case T_ELSE: case T_AS: case T_LNUMBER: case T_CONSTANT_ENCAPSED_STRING: case T_PUBLIC: case T_PROTECTED: case T_PRIVATE: case T_VAR: break; default: /* debug unknown tags*/ printf("%d (%s): %s".PHP_EOL, $token[0], token_name($token[0]), $token[1]); break; } if (!$ignore) { if (!$first_token) $first_token = $token[0]; $eval .= $token[1]." "; $last_token = $token[0]; } } else { switch ($token) { case '(': /* function */ if ($is_class && !class_exists($last_string)) { throw new Exception(sprintf("Class %s doesn't exist", $last_string)); } if ($is_function && !function_exists($last_string)) { throw new Exception(sprintf("Function %s() doesn't exist", $last_string)); } $is_function = 1; $is_class = 0; array_push($braces, $token); break; case '{': if ($is_class) { if (class_exists($last_string, false)) { /* the class is already there */ throw new Exception(sprintf("Class %s is already existing", $last_string)); } } if ($is_extends) { if (!class_exists($last_string, false)) { /* the class is already there */ throw new Exception(sprintf("Class %s doens't exists", $last_string)); } } $is_class = 0; array_push($braces, $token); break; case '}': array_pop($braces); break; case ')': array_pop($braces); break; } $eval .= $token; $last_token = $token; } } $need_more = count($braces); if ($need_more || ';' == $last_token) { $need_comma = 0; } if ($need_return) { $eval = "return ".$eval; } if ($need_comma) { $eval .= ';'; } if (!$need_more) { $this->code = $eval; } return $need_more; } /** fetch a single line overwrite this for readline support */ public function readline() { if (empty($this->code)) print PHP_EOL; $prompt = (empty($this->code)) ? '>> ' : '.. '; if ($this->have_readline) { $l = readline($prompt); readline_add_history($l); } else { print $prompt; $l = fgets($this->stdin); } return $l; } /** handle the input line - generate prompt - handle 'quit' and 'p' */ public function input() { $l = $this->readline(); if (false === $l) return false; if ("quit" == rtrim($l)) return false; $cmd = $l; $this->verbose = 0; if ("p " == substr($l, 0, strlen("p "))) { $cmd = substr($l, 2); $this->verbose = 1; } $this->code .= $cmd; return true; } /** simple gettors */ public function getCode() { return $this->code; } public function resetCode() { $this->code = ''; } public function getVerbose() { return $this->verbose; } }