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>