Return to php_cli_readline.c CVS log | Up to [ELWIX - Embedded LightWeight unIX -] / embedaddon / php / sapi / cli |
1.1 ! misho 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 321634 2012-01-01 13:15:04Z felipe $ */ ! 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: */