xref: /PHP-5.3/sapi/cli/php_cli_readline.c (revision a2045ff3)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2013 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$ */
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 
cli_is_valid_code(char * code,int len,char ** prompt TSRMLS_DC)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 
cli_completion_generator_ht(const char * text,int textlen,int * state,HashTable * ht,void ** pData TSRMLS_DC)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 
cli_completion_generator_var(const char * text,int textlen,int * state TSRMLS_DC)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 
cli_completion_generator_func(const char * text,int textlen,int * state,HashTable * ht TSRMLS_DC)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 
cli_completion_generator_class(const char * text,int textlen,int * state TSRMLS_DC)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 
cli_completion_generator_define(const char * text,int textlen,int * state,HashTable * ht TSRMLS_DC)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 
cli_completion_generator(const char * text,int index)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 
cli_code_completion(const char * text,int start,int end)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  */
449