File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / php / sapi / cli / php_cli_readline.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:48:06 2012 UTC (12 years, 6 months ago) by misho
Branches: php, MAIN
CVS tags: v5_3_10, HEAD
php

    1: /*
    2:    +----------------------------------------------------------------------+
    3:    | PHP Version 5                                                        |
    4:    +----------------------------------------------------------------------+
    5:    | Copyright (c) 1997-2012 The PHP Group                                |
    6:    +----------------------------------------------------------------------+
    7:    | This source file is subject to version 3.01 of the PHP license,      |
    8:    | that is bundled with this package in the file LICENSE, and is        |
    9:    | available through the world-wide-web at the following url:           |
   10:    | http://www.php.net/license/3_01.txt                                  |
   11:    | If you did not receive a copy of the PHP license and are unable to   |
   12:    | obtain it through the world-wide-web, please send a note to          |
   13:    | license@php.net so we can mail you a copy immediately.               |
   14:    +----------------------------------------------------------------------+
   15:    | Author: Marcus Boerger <helly@php.net>                               |
   16:    |         Johannes Schlueter <johannes@php.net>                        |
   17:    +----------------------------------------------------------------------+
   18: */
   19: 
   20: /* $Id: php_cli_readline.c,v 1.1.1.1 2012/02/21 23:48:06 misho Exp $ */
   21: 
   22: #include "php.h"
   23: 
   24: #if (HAVE_LIBREADLINE || HAVE_LIBEDIT) && !defined(COMPILE_DL_READLINE)
   25: 
   26: #ifndef HAVE_RL_COMPLETION_MATCHES
   27: #define rl_completion_matches completion_matches
   28: #endif
   29: 
   30: #include "php_globals.h"
   31: #include "php_variables.h"
   32: #include "zend_hash.h"
   33: #include "zend_modules.h"
   34: 
   35: #include "SAPI.h"
   36: 
   37: #if HAVE_SETLOCALE
   38: #include <locale.h>
   39: #endif
   40: #include "zend.h"
   41: #include "zend_extensions.h"
   42: #include "php_ini.h"
   43: #include "php_globals.h"
   44: #include "php_main.h"
   45: #include "fopen_wrappers.h"
   46: #include "ext/standard/php_standard.h"
   47: 
   48: #ifdef __riscos__
   49: #include <unixlib/local.h>
   50: #endif
   51: 
   52: #if HAVE_LIBEDIT
   53: #include <editline/readline.h>
   54: #else
   55: #include <readline/readline.h>
   56: #include <readline/history.h>
   57: #endif
   58: 
   59: #include "zend_compile.h"
   60: #include "zend_execute.h"
   61: #include "zend_highlight.h"
   62: #include "zend_indent.h"
   63: 
   64: typedef enum {
   65: 	body,
   66: 	sstring,
   67: 	dstring,
   68: 	sstring_esc,
   69: 	dstring_esc,
   70: 	comment_line,
   71: 	comment_block,
   72: 	heredoc_start,
   73: 	heredoc,
   74: 	outside,
   75: } php_code_type;
   76: 
   77: int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC) /* {{{ */
   78: {
   79: 	int valid_end = 1, last_valid_end;
   80: 	int brackets_count = 0;
   81: 	int brace_count = 0;
   82: 	int i;
   83: 	php_code_type code_type = body;
   84: 	char *heredoc_tag;
   85: 	int heredoc_len;
   86: 
   87: 	for (i = 0; i < len; ++i) {
   88: 		switch(code_type) {
   89: 			default:
   90: 				switch(code[i]) {
   91: 					case '{':
   92: 						brackets_count++;
   93: 						valid_end = 0;
   94: 						break;
   95: 					case '}':
   96: 						if (brackets_count > 0) {
   97: 							brackets_count--;
   98: 						}
   99: 						valid_end = brackets_count ? 0 : 1;
  100: 						break;
  101: 					case '(':
  102: 						brace_count++;
  103: 						valid_end = 0;
  104: 						break;
  105: 					case ')':
  106: 						if (brace_count > 0) {
  107: 							brace_count--;
  108: 						}
  109: 						valid_end = 0;
  110: 						break;
  111: 					case ';':
  112: 						valid_end = brace_count == 0 && brackets_count == 0;
  113: 						break;
  114: 					case ' ':
  115: 					case '\r':
  116: 					case '\n':
  117: 					case '\t':
  118: 						break;
  119: 					case '\'':
  120: 						code_type = sstring;
  121: 						break;
  122: 					case '"':
  123: 						code_type = dstring;
  124: 						break;
  125: 					case '#':
  126: 						code_type = comment_line;
  127: 						break;
  128: 					case '/':
  129: 						if (code[i+1] == '/') {
  130: 							i++;
  131: 							code_type = comment_line;
  132: 							break;
  133: 						}
  134: 						if (code[i+1] == '*') {
  135: 							last_valid_end = valid_end;
  136: 							valid_end = 0;
  137: 							code_type = comment_block;
  138: 							i++;
  139: 							break;
  140: 						}
  141: 						valid_end = 0;
  142: 						break;
  143: 					case '%':
  144: 						if (!CG(asp_tags)) {
  145: 							valid_end = 0;
  146: 							break;
  147: 						}
  148: 						/* no break */
  149: 					case '?':
  150: 						if (code[i+1] == '>') {
  151: 							i++;
  152: 							code_type = outside;
  153: 							break;
  154: 						}
  155: 						valid_end = 0;
  156: 						break;
  157: 					case '<':
  158: 						valid_end = 0;
  159: 						if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {
  160: 							i += 2;
  161: 							code_type = heredoc_start;
  162: 							heredoc_len = 0;
  163: 						}
  164: 						break;
  165: 					default:
  166: 						valid_end = 0;
  167: 						break;
  168: 				}
  169: 				break;
  170: 			case sstring:
  171: 				if (code[i] == '\\') {
  172: 					code_type = sstring_esc;
  173: 				} else {
  174: 					if (code[i] == '\'') {
  175: 						code_type = body;
  176: 					}
  177: 				}
  178: 				break;
  179: 			case sstring_esc:
  180: 				code_type = sstring;
  181: 				break;
  182: 			case dstring:
  183: 				if (code[i] == '\\') {
  184: 					code_type = dstring_esc;
  185: 				} else {
  186: 					if (code[i] == '"') {
  187: 						code_type = body;
  188: 					}
  189: 				}
  190: 				break;
  191: 			case dstring_esc:
  192: 				code_type = dstring;
  193: 				break;
  194: 			case comment_line:
  195: 				if (code[i] == '\n') {
  196: 					code_type = body;
  197: 				}
  198: 				break;
  199: 			case comment_block:
  200: 				if (code[i-1] == '*' && code[i] == '/') {
  201: 					code_type = body;
  202: 					valid_end = last_valid_end;
  203: 				}
  204: 				break;
  205: 			case heredoc_start:
  206: 				switch(code[i]) {
  207: 					case ' ':
  208: 					case '\t':
  209: 						break;
  210: 					case '\r':
  211: 					case '\n':
  212: 						code_type = heredoc;
  213: 						break;
  214: 					default:
  215: 						if (!heredoc_len) {
  216: 							heredoc_tag = code+i;
  217: 						}
  218: 						heredoc_len++;
  219: 						break;
  220: 				}
  221: 				break;
  222: 			case heredoc:
  223: 				if (code[i - (heredoc_len + 1)] == '\n' && !strncmp(code + i - heredoc_len, heredoc_tag, heredoc_len) && code[i] == '\n') {
  224: 					code_type = body;
  225: 				} else if (code[i - (heredoc_len + 2)] == '\n' && !strncmp(code + i - heredoc_len - 1, heredoc_tag, heredoc_len) && code[i-1] == ';' && code[i] == '\n') {
  226: 					code_type = body;
  227: 					valid_end = 1;
  228: 				}
  229: 				break;
  230: 			case outside:
  231: 				if ((CG(short_tags) && !strncmp(code+i-1, "<?", 2))
  232: 				||  (CG(asp_tags) && !strncmp(code+i-1, "<%", 2))
  233: 				||  (i > 3 && !strncmp(code+i-4, "<?php", 5))
  234: 				) {
  235: 					code_type = body;
  236: 				}
  237: 				break;
  238: 		}
  239: 	}
  240: 
  241: 	switch (code_type) {
  242: 		default:
  243: 			if (brace_count) {
  244: 				*prompt = "php ( ";
  245: 			} else if (brackets_count) {
  246: 				*prompt = "php { ";
  247: 			} else {
  248: 				*prompt = "php > ";
  249: 			}
  250: 			break;
  251: 		case sstring:
  252: 		case sstring_esc:
  253: 			*prompt = "php ' ";
  254: 			break;
  255: 		case dstring:
  256: 		case dstring_esc:
  257: 			*prompt = "php \" ";
  258: 			break;
  259: 		case comment_block:
  260: 			*prompt = "/*  > ";
  261: 			break;
  262: 		case heredoc:
  263: 			*prompt = "<<< > ";
  264: 			break;
  265: 		case outside:
  266: 			*prompt = "    > ";
  267: 			break;
  268: 	}
  269: 
  270: 	if (!valid_end || brackets_count) {
  271: 		return 0;
  272: 	} else {
  273: 		return 1;
  274: 	}
  275: }
  276: /* }}} */
  277: 
  278: static char *cli_completion_generator_ht(const char *text, int textlen, int *state, HashTable *ht, void **pData TSRMLS_DC) /* {{{ */
  279: {
  280: 	char *name;
  281: 	ulong number;
  282: 
  283: 	if (!(*state % 2)) {
  284: 		zend_hash_internal_pointer_reset(ht);
  285: 		(*state)++;
  286: 	}
  287: 	while(zend_hash_has_more_elements(ht) == SUCCESS) {
  288: 		zend_hash_get_current_key(ht, &name, &number, 0);
  289: 		if (!textlen || !strncmp(name, text, textlen)) {
  290: 			if (pData) {
  291: 				zend_hash_get_current_data(ht, pData);
  292: 			}
  293: 			zend_hash_move_forward(ht);
  294: 			return name;
  295: 		}
  296: 		if (zend_hash_move_forward(ht) == FAILURE) {
  297: 			break;
  298: 		}
  299: 	}
  300: 	(*state)++;
  301: 	return NULL;
  302: } /* }}} */
  303: 
  304: static char *cli_completion_generator_var(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
  305: {
  306: 	char *retval, *tmp;
  307: 
  308: 	tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(active_symbol_table), NULL TSRMLS_CC);
  309: 	if (retval) {
  310: 		retval = malloc(strlen(tmp) + 2);
  311: 		retval[0] = '$';
  312: 		strcpy(&retval[1], tmp);
  313: 		rl_completion_append_character = '\0';
  314: 	}
  315: 	return retval;
  316: } /* }}} */
  317: 
  318: static char *cli_completion_generator_func(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */
  319: {
  320: 	zend_function *func;
  321: 	char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func TSRMLS_CC);
  322: 	if (retval) {
  323: 		rl_completion_append_character = '(';
  324: 		retval = strdup(func->common.function_name);
  325: 	}
  326: 	
  327: 	return retval;
  328: } /* }}} */
  329: 
  330: static char *cli_completion_generator_class(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
  331: {
  332: 	zend_class_entry **pce;
  333: 	char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&pce TSRMLS_CC);
  334: 	if (retval) {
  335: 		rl_completion_append_character = '\0';
  336: 		retval = strdup((*pce)->name);
  337: 	}
  338: 	
  339: 	return retval;
  340: } /* }}} */
  341: 
  342: static char *cli_completion_generator_define(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */
  343: {
  344: 	zend_class_entry **pce;
  345: 	char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce TSRMLS_CC);
  346: 	if (retval) {
  347: 		rl_completion_append_character = '\0';
  348: 		retval = strdup(retval);
  349: 	}
  350: 	
  351: 	return retval;
  352: } /* }}} */
  353: 
  354: static int cli_completion_state;
  355: 
  356: static char *cli_completion_generator(const char *text, int index) /* {{{ */
  357: {
  358: /*
  359: TODO:
  360: - constants
  361: - maybe array keys
  362: - language constructs and other things outside a hashtable (echo, try, function, class, ...)
  363: - object/class members
  364: 
  365: - future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)
  366: */
  367: 	char *retval = NULL;
  368: 	int textlen = strlen(text);
  369: 	TSRMLS_FETCH();
  370: 
  371: 	if (!index) {
  372: 		cli_completion_state = 0;
  373: 	}
  374: 	if (text[0] == '$') {
  375: 		retval = cli_completion_generator_var(text, textlen, &cli_completion_state TSRMLS_CC);
  376: 	} else {
  377: 		char *lc_text, *class_name, *class_name_end;
  378: 		int class_name_len;
  379: 		zend_class_entry **pce = NULL;
  380: 		
  381: 		class_name_end = strstr(text, "::");
  382: 		if (class_name_end) {
  383: 			class_name_len = class_name_end - text;
  384: 			class_name = zend_str_tolower_dup(text, class_name_len);
  385: 			class_name[class_name_len] = '\0'; /* not done automatically */
  386: 			if (zend_lookup_class(class_name, class_name_len, &pce TSRMLS_CC)==FAILURE) {
  387: 				efree(class_name);
  388: 				return NULL;
  389: 			}
  390: 			lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
  391: 			textlen -= (class_name_len + 2);
  392: 		} else {
  393: 			lc_text = zend_str_tolower_dup(text, textlen);
  394: 		}
  395: 
  396: 		switch (cli_completion_state) {
  397: 			case 0:
  398: 			case 1:
  399: 				retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, pce ? &(*pce)->function_table : EG(function_table) TSRMLS_CC);
  400: 				if (retval) {
  401: 					break;
  402: 				}
  403: 			case 2:
  404: 			case 3:
  405: 				retval = cli_completion_generator_define(text, textlen, &cli_completion_state, pce ? &(*pce)->constants_table : EG(zend_constants) TSRMLS_CC);
  406: 				if (retval || pce) {
  407: 					break;
  408: 				}
  409: 			case 4:
  410: 			case 5:
  411: 				retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state TSRMLS_CC);
  412: 				break;
  413: 			default:
  414: 				break;
  415: 		}
  416: 		efree(lc_text);
  417: 		if (class_name_end) {
  418: 			efree(class_name);
  419: 		}
  420: 		if (pce && retval) {
  421: 			int len = class_name_len + 2 + strlen(retval) + 1;
  422: 			char *tmp = malloc(len);
  423: 			
  424: 			snprintf(tmp, len, "%s::%s", (*pce)->name, retval);
  425: 			free(retval);
  426: 			retval = tmp;
  427: 		}
  428: 	}
  429: 	
  430: 	return retval;
  431: } /* }}} */
  432: 
  433: char **cli_code_completion(const char *text, int start, int end) /* {{{ */
  434: {
  435: 	return rl_completion_matches(text, cli_completion_generator);
  436: }
  437: /* }}} */
  438: 
  439: #endif /* HAVE_LIBREADLINE || HAVE_LIBEDIT */
  440: 
  441: /*
  442:  * Local variables:
  443:  * tab-width: 4
  444:  * c-basic-offset: 4
  445:  * End:
  446:  * vim600: sw=4 ts=4 fdm=marker
  447:  * vim<600: sw=4 ts=4
  448:  */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>