--- /dev/null
+/*\r
+ +----------------------------------------------------------------------+\r
+ | PHP Version 5 |\r
+ +----------------------------------------------------------------------+\r
+ | Copyright (c) 1997-2011 The PHP Group |\r
+ +----------------------------------------------------------------------+\r
+ | This source file is subject to version 3.01 of the PHP license, |\r
+ | that is bundled with this package in the file LICENSE, and is |\r
+ | available through the world-wide-web at the following url: |\r
+ | http://www.php.net/license/3_01.txt |\r
+ | If you did not receive a copy of the PHP license and are unable to |\r
+ | obtain it through the world-wide-web, please send a note to |\r
+ | license@php.net so we can mail you a copy immediately. |\r
+ +----------------------------------------------------------------------+\r
+ | Author: Marcus Boerger <helly@php.net> |\r
+ | Johannes Schlueter <johannes@php.net> |\r
+ +----------------------------------------------------------------------+\r
+*/\r
+\r
+/* $Id: php_cli_readline.c 306939 2011-01-01 02:19:59Z felipe $ */\r
+\r
+#include "php.h"\r
+\r
+#if (HAVE_LIBREADLINE || HAVE_LIBEDIT) && !defined(COMPILE_DL_READLINE)\r
+\r
+#ifndef HAVE_RL_COMPLETION_MATCHES\r
+#define rl_completion_matches completion_matches\r
+#endif\r
+\r
+#include "php_globals.h"\r
+#include "php_variables.h"\r
+#include "zend_hash.h"\r
+#include "zend_modules.h"\r
+\r
+#include "SAPI.h"\r
+\r
+#if HAVE_SETLOCALE\r
+#include <locale.h>\r
+#endif\r
+#include "zend.h"\r
+#include "zend_extensions.h"\r
+#include "php_ini.h"\r
+#include "php_globals.h"\r
+#include "php_main.h"\r
+#include "fopen_wrappers.h"\r
+#include "ext/standard/php_standard.h"\r
+\r
+#ifdef __riscos__\r
+#include <unixlib/local.h>\r
+#endif\r
+\r
+#if HAVE_LIBEDIT\r
+#include <editline/readline.h>\r
+#else\r
+#include <readline/readline.h>\r
+#include <readline/history.h>\r
+#endif\r
+\r
+#include "zend_compile.h"\r
+#include "zend_execute.h"\r
+#include "zend_highlight.h"\r
+#include "zend_indent.h"\r
+\r
+typedef enum {\r
+ body,\r
+ sstring,\r
+ dstring,\r
+ sstring_esc,\r
+ dstring_esc,\r
+ comment_line,\r
+ comment_block,\r
+ heredoc_start,\r
+ heredoc,\r
+ outside,\r
+} php_code_type;\r
+\r
+int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC) /* {{{ */\r
+{\r
+ int valid_end = 1, last_valid_end;\r
+ int brackets_count = 0;\r
+ int brace_count = 0;\r
+ int i;\r
+ php_code_type code_type = body;\r
+ char *heredoc_tag;\r
+ int heredoc_len;\r
+\r
+ for (i = 0; i < len; ++i) {\r
+ switch(code_type) {\r
+ default:\r
+ switch(code[i]) {\r
+ case '{':\r
+ brackets_count++;\r
+ valid_end = 0;\r
+ break;\r
+ case '}':\r
+ if (brackets_count > 0) {\r
+ brackets_count--;\r
+ }\r
+ valid_end = brackets_count ? 0 : 1;\r
+ break;\r
+ case '(':\r
+ brace_count++;\r
+ valid_end = 0;\r
+ break;\r
+ case ')':\r
+ if (brace_count > 0) {\r
+ brace_count--;\r
+ }\r
+ valid_end = 0;\r
+ break;\r
+ case ';':\r
+ valid_end = brace_count == 0 && brackets_count == 0;\r
+ break;\r
+ case ' ':\r
+ case '\r':\r
+ case '\n':\r
+ case '\t':\r
+ break;\r
+ case '\'':\r
+ code_type = sstring;\r
+ break;\r
+ case '"':\r
+ code_type = dstring;\r
+ break;\r
+ case '#':\r
+ code_type = comment_line;\r
+ break;\r
+ case '/':\r
+ if (code[i+1] == '/') {\r
+ i++;\r
+ code_type = comment_line;\r
+ break;\r
+ }\r
+ if (code[i+1] == '*') {\r
+ last_valid_end = valid_end;\r
+ valid_end = 0;\r
+ code_type = comment_block;\r
+ i++;\r
+ break;\r
+ }\r
+ valid_end = 0;\r
+ break;\r
+ case '%':\r
+ if (!CG(asp_tags)) {\r
+ valid_end = 0;\r
+ break;\r
+ }\r
+ /* no break */\r
+ case '?':\r
+ if (code[i+1] == '>') {\r
+ i++;\r
+ code_type = outside;\r
+ break;\r
+ }\r
+ valid_end = 0;\r
+ break;\r
+ case '<':\r
+ valid_end = 0;\r
+ if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {\r
+ i += 2;\r
+ code_type = heredoc_start;\r
+ heredoc_len = 0;\r
+ }\r
+ break;\r
+ default:\r
+ valid_end = 0;\r
+ break;\r
+ }\r
+ break;\r
+ case sstring:\r
+ if (code[i] == '\\') {\r
+ code_type = sstring_esc;\r
+ } else {\r
+ if (code[i] == '\'') {\r
+ code_type = body;\r
+ }\r
+ }\r
+ break;\r
+ case sstring_esc:\r
+ code_type = sstring;\r
+ break;\r
+ case dstring:\r
+ if (code[i] == '\\') {\r
+ code_type = dstring_esc;\r
+ } else {\r
+ if (code[i] == '"') {\r
+ code_type = body;\r
+ }\r
+ }\r
+ break;\r
+ case dstring_esc:\r
+ code_type = dstring;\r
+ break;\r
+ case comment_line:\r
+ if (code[i] == '\n') {\r
+ code_type = body;\r
+ }\r
+ break;\r
+ case comment_block:\r
+ if (code[i-1] == '*' && code[i] == '/') {\r
+ code_type = body;\r
+ valid_end = last_valid_end;\r
+ }\r
+ break;\r
+ case heredoc_start:\r
+ switch(code[i]) {\r
+ case ' ':\r
+ case '\t':\r
+ break;\r
+ case '\r':\r
+ case '\n':\r
+ code_type = heredoc;\r
+ break;\r
+ default:\r
+ if (!heredoc_len) {\r
+ heredoc_tag = code+i;\r
+ }\r
+ heredoc_len++;\r
+ break;\r
+ }\r
+ break;\r
+ case heredoc:\r
+ if (code[i - (heredoc_len + 1)] == '\n' && !strncmp(code + i - heredoc_len, heredoc_tag, heredoc_len) && code[i] == '\n') {\r
+ code_type = body;\r
+ } else if (code[i - (heredoc_len + 2)] == '\n' && !strncmp(code + i - heredoc_len - 1, heredoc_tag, heredoc_len) && code[i-1] == ';' && code[i] == '\n') {\r
+ code_type = body;\r
+ valid_end = 1;\r
+ }\r
+ break;\r
+ case outside:\r
+ if ((CG(short_tags) && !strncmp(code+i-1, "<?", 2))\r
+ || (CG(asp_tags) && !strncmp(code+i-1, "<%", 2))\r
+ || (i > 3 && !strncmp(code+i-4, "<?php", 5))\r
+ ) {\r
+ code_type = body;\r
+ }\r
+ break;\r
+ }\r
+ }\r
+\r
+ switch (code_type) {\r
+ default:\r
+ if (brace_count) {\r
+ *prompt = "php ( ";\r
+ } else if (brackets_count) {\r
+ *prompt = "php { ";\r
+ } else {\r
+ *prompt = "php > ";\r
+ }\r
+ break;\r
+ case sstring:\r
+ case sstring_esc:\r
+ *prompt = "php ' ";\r
+ break;\r
+ case dstring:\r
+ case dstring_esc:\r
+ *prompt = "php \" ";\r
+ break;\r
+ case comment_block:\r
+ *prompt = "/* > ";\r
+ break;\r
+ case heredoc:\r
+ *prompt = "<<< > ";\r
+ break;\r
+ case outside:\r
+ *prompt = " > ";\r
+ break;\r
+ }\r
+\r
+ if (!valid_end || brackets_count) {\r
+ return 0;\r
+ } else {\r
+ return 1;\r
+ }\r
+}\r
+/* }}} */\r
+\r
+static char *cli_completion_generator_ht(const char *text, int textlen, int *state, HashTable *ht, void **pData TSRMLS_DC) /* {{{ */\r
+{\r
+ char *name;\r
+ ulong number;\r
+\r
+ if (!(*state % 2)) {\r
+ zend_hash_internal_pointer_reset(ht);\r
+ (*state)++;\r
+ }\r
+ while(zend_hash_has_more_elements(ht) == SUCCESS) {\r
+ zend_hash_get_current_key(ht, &name, &number, 0);\r
+ if (!textlen || !strncmp(name, text, textlen)) {\r
+ if (pData) {\r
+ zend_hash_get_current_data(ht, pData);\r
+ }\r
+ zend_hash_move_forward(ht);\r
+ return name;\r
+ }\r
+ if (zend_hash_move_forward(ht) == FAILURE) {\r
+ break;\r
+ }\r
+ }\r
+ (*state)++;\r
+ return NULL;\r
+} /* }}} */\r
+\r
+static char *cli_completion_generator_var(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */\r
+{\r
+ char *retval, *tmp;\r
+\r
+ tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(active_symbol_table), NULL TSRMLS_CC);\r
+ if (retval) {\r
+ retval = malloc(strlen(tmp) + 2);\r
+ retval[0] = '$';\r
+ strcpy(&retval[1], tmp);\r
+ rl_completion_append_character = '\0';\r
+ }\r
+ return retval;\r
+} /* }}} */\r
+\r
+static char *cli_completion_generator_func(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */\r
+{\r
+ zend_function *func;\r
+ char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func TSRMLS_CC);\r
+ if (retval) {\r
+ rl_completion_append_character = '(';\r
+ retval = strdup(func->common.function_name);\r
+ }\r
+ \r
+ return retval;\r
+} /* }}} */\r
+\r
+static char *cli_completion_generator_class(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */\r
+{\r
+ zend_class_entry **pce;\r
+ char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&pce TSRMLS_CC);\r
+ if (retval) {\r
+ rl_completion_append_character = '\0';\r
+ retval = strdup((*pce)->name);\r
+ }\r
+ \r
+ return retval;\r
+} /* }}} */\r
+\r
+static char *cli_completion_generator_define(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */\r
+{\r
+ zend_class_entry **pce;\r
+ char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce TSRMLS_CC);\r
+ if (retval) {\r
+ rl_completion_append_character = '\0';\r
+ retval = strdup(retval);\r
+ }\r
+ \r
+ return retval;\r
+} /* }}} */\r
+\r
+static int cli_completion_state;\r
+\r
+static char *cli_completion_generator(const char *text, int index) /* {{{ */\r
+{\r
+/*\r
+TODO:\r
+- constants\r
+- maybe array keys\r
+- language constructs and other things outside a hashtable (echo, try, function, class, ...)\r
+- object/class members\r
+\r
+- future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)\r
+*/\r
+ char *retval = NULL;\r
+ int textlen = strlen(text);\r
+ TSRMLS_FETCH();\r
+\r
+ if (!index) {\r
+ cli_completion_state = 0;\r
+ }\r
+ if (text[0] == '$') {\r
+ retval = cli_completion_generator_var(text, textlen, &cli_completion_state TSRMLS_CC);\r
+ } else {\r
+ char *lc_text, *class_name, *class_name_end;\r
+ int class_name_len;\r
+ zend_class_entry **pce = NULL;\r
+ \r
+ class_name_end = strstr(text, "::");\r
+ if (class_name_end) {\r
+ class_name_len = class_name_end - text;\r
+ class_name = zend_str_tolower_dup(text, class_name_len);\r
+ class_name[class_name_len] = '\0'; /* not done automatically */\r
+ if (zend_lookup_class(class_name, class_name_len, &pce TSRMLS_CC)==FAILURE) {\r
+ efree(class_name);\r
+ return NULL;\r
+ }\r
+ lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);\r
+ textlen -= (class_name_len + 2);\r
+ } else {\r
+ lc_text = zend_str_tolower_dup(text, textlen);\r
+ }\r
+\r
+ switch (cli_completion_state) {\r
+ case 0:\r
+ case 1:\r
+ retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, pce ? &(*pce)->function_table : EG(function_table) TSRMLS_CC);\r
+ if (retval) {\r
+ break;\r
+ }\r
+ case 2:\r
+ case 3:\r
+ retval = cli_completion_generator_define(text, textlen, &cli_completion_state, pce ? &(*pce)->constants_table : EG(zend_constants) TSRMLS_CC);\r
+ if (retval || pce) {\r
+ break;\r
+ }\r
+ case 4:\r
+ case 5:\r
+ retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state TSRMLS_CC);\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+ efree(lc_text);\r
+ if (class_name_end) {\r
+ efree(class_name);\r
+ }\r
+ if (pce && retval) {\r
+ int len = class_name_len + 2 + strlen(retval) + 1;\r
+ char *tmp = malloc(len);\r
+ \r
+ snprintf(tmp, len, "%s::%s", (*pce)->name, retval);\r
+ free(retval);\r
+ retval = tmp;\r
+ }\r
+ }\r
+ \r
+ return retval;\r
+} /* }}} */\r
+\r
+char **cli_code_completion(const char *text, int start, int end) /* {{{ */\r
+{\r
+ return rl_completion_matches(text, cli_completion_generator);\r
+}\r
+/* }}} */\r
+\r
+#endif /* HAVE_LIBREADLINE || HAVE_LIBEDIT */\r
+\r
+/*\r
+ * Local variables:\r
+ * tab-width: 4\r
+ * c-basic-offset: 4\r
+ * End:\r
+ * vim600: sw=4 ts=4 fdm=marker\r
+ * vim<600: sw=4 ts=4\r
+ */\r