1 : /*
2 : +----------------------------------------------------------------------+
3 : | PHP Version 6 |
4 : +----------------------------------------------------------------------+
5 : | Copyright (c) 1997-2009 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 277888 2009-03-27 19:50:56Z 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 : #include <readline/readline.h>
53 : #if !HAVE_LIBEDIT
54 : #include <readline/history.h>
55 : #endif
56 :
57 : #include "zend_compile.h"
58 : #include "zend_execute.h"
59 : #include "zend_highlight.h"
60 : #include "zend_indent.h"
61 :
62 : /* {{{ cli_is_valid_code
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 18 : {
79 18 : int valid_end = 1, last_valid_end;
80 18 : int brackets_count = 0;
81 18 : int brace_count = 0;
82 : int i;
83 18 : php_code_type code_type = body;
84 : char *heredoc_tag;
85 : int heredoc_len;
86 :
87 558 : for (i = 0; i < len; ++i) {
88 540 : switch(code_type) {
89 : default:
90 297 : switch(code[i]) {
91 : case '{':
92 6 : brackets_count++;
93 6 : valid_end = 0;
94 6 : break;
95 : case '}':
96 2 : if (brackets_count > 0) {
97 2 : brackets_count--;
98 : }
99 2 : valid_end = brackets_count ? 0 : 1;
100 2 : break;
101 : case '(':
102 7 : brace_count++;
103 7 : valid_end = 0;
104 7 : break;
105 : case ')':
106 7 : if (brace_count > 0) {
107 7 : brace_count--;
108 : }
109 7 : valid_end = 0;
110 7 : break;
111 : case ';':
112 8 : valid_end = brace_count == 0 && brackets_count == 0;
113 8 : break;
114 : case ' ':
115 : case '\r':
116 : case '\n':
117 : case '\t':
118 59 : break;
119 : case '\'':
120 4 : code_type = sstring;
121 4 : break;
122 : case '"':
123 5 : code_type = dstring;
124 5 : break;
125 : case '#':
126 0 : code_type = comment_line;
127 0 : break;
128 : case '/':
129 0 : if (code[i+1] == '/') {
130 0 : i++;
131 0 : code_type = comment_line;
132 0 : break;
133 : }
134 0 : if (code[i+1] == '*') {
135 0 : last_valid_end = valid_end;
136 0 : valid_end = 0;
137 0 : code_type = comment_block;
138 0 : i++;
139 0 : break;
140 : }
141 0 : valid_end = 0;
142 0 : break;
143 : case '%':
144 0 : if (!CG(asp_tags)) {
145 0 : valid_end = 0;
146 0 : break;
147 : }
148 : /* no break */
149 : case '?':
150 0 : if (code[i+1] == '>') {
151 0 : i++;
152 0 : code_type = outside;
153 0 : break;
154 : }
155 0 : valid_end = 0;
156 0 : break;
157 : case '<':
158 6 : valid_end = 0;
159 6 : if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {
160 6 : i += 2;
161 6 : code_type = heredoc_start;
162 6 : heredoc_len = 0;
163 : }
164 6 : break;
165 : default:
166 193 : valid_end = 0;
167 : break;
168 : }
169 297 : break;
170 : case sstring:
171 56 : if (code[i] == '\\') {
172 0 : code_type = sstring_esc;
173 : } else {
174 56 : if (code[i] == '\'') {
175 2 : code_type = body;
176 : }
177 : }
178 56 : break;
179 : case sstring_esc:
180 0 : code_type = sstring;
181 0 : break;
182 : case dstring:
183 61 : if (code[i] == '\\') {
184 0 : code_type = dstring_esc;
185 : } else {
186 61 : if (code[i] == '"') {
187 5 : code_type = body;
188 : }
189 : }
190 61 : break;
191 : case dstring_esc:
192 0 : code_type = dstring;
193 0 : break;
194 : case comment_line:
195 0 : if (code[i] == '\n') {
196 0 : code_type = body;
197 : }
198 0 : break;
199 : case comment_block:
200 0 : if (code[i-1] == '*' && code[i] == '/') {
201 0 : code_type = body;
202 0 : valid_end = last_valid_end;
203 : }
204 0 : break;
205 : case heredoc_start:
206 48 : switch(code[i]) {
207 : case ' ':
208 : case '\t':
209 0 : break;
210 : case '\r':
211 : case '\n':
212 6 : code_type = heredoc;
213 6 : break;
214 : default:
215 42 : if (!heredoc_len) {
216 6 : heredoc_tag = code+i;
217 : }
218 42 : heredoc_len++;
219 : break;
220 : }
221 48 : break;
222 : case heredoc:
223 78 : if (code[i - (heredoc_len + 1)] == '\n' && !strncmp(code + i - heredoc_len, heredoc_tag, heredoc_len) && code[i] == '\n') {
224 0 : code_type = body;
225 78 : } 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 1 : code_type = body;
227 1 : valid_end = 1;
228 : }
229 78 : break;
230 : case outside:
231 0 : 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 0 : code_type = body;
236 : }
237 : break;
238 : }
239 : }
240 :
241 18 : switch (code_type) {
242 : default:
243 11 : if (brace_count) {
244 0 : *prompt = "php ( ";
245 11 : } else if (brackets_count) {
246 4 : *prompt = "php { ";
247 : } else {
248 7 : *prompt = "php > ";
249 : }
250 11 : break;
251 : case sstring:
252 : case sstring_esc:
253 2 : *prompt = "php ' ";
254 2 : break;
255 : case dstring:
256 : case dstring_esc:
257 0 : *prompt = "php \" ";
258 0 : break;
259 : case comment_block:
260 0 : *prompt = "/* > ";
261 0 : break;
262 : case heredoc:
263 5 : *prompt = "<<< > ";
264 5 : break;
265 : case outside:
266 0 : *prompt = " > ";
267 : break;
268 : }
269 :
270 18 : if (!valid_end || brackets_count) {
271 11 : return 0;
272 : } else {
273 7 : 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 4 : {
280 : zstr name;
281 : ulong number;
282 :
283 4 : if (!(*state % 2)) {
284 3 : zend_hash_internal_pointer_reset(ht);
285 3 : (*state)++;
286 : }
287 4183 : while(zend_hash_has_more_elements(ht) == SUCCESS) {
288 4176 : zend_hash_get_current_key(ht, &name, &number, 0);
289 4176 : if (!textlen || !zend_cmp_unicode_and_string(name.u, (char *)text, textlen)) {
290 1 : if (pData) {
291 1 : zend_hash_get_current_data(ht, pData);
292 : }
293 1 : zend_hash_move_forward(ht);
294 1 : return name.s;
295 : }
296 4175 : if (zend_hash_move_forward(ht) == FAILURE) {
297 0 : break;
298 : }
299 : }
300 3 : (*state)++;
301 3 : return NULL;
302 : } /* }}} */
303 :
304 : static char *cli_completion_generator_var(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
305 0 : {
306 : char *retval, *tmp;
307 :
308 0 : tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(active_symbol_table), NULL TSRMLS_CC);
309 0 : if (retval) {
310 : int32_t tmp_len, len;
311 0 : UErrorCode status = U_ZERO_ERROR;
312 :
313 0 : len = u_strlen((UChar *)retval);
314 0 : zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(output_encoding_conv)), &tmp, &tmp_len,
315 : (UChar *)retval, len, &status);
316 :
317 0 : retval = malloc(tmp_len + 2);
318 0 : retval[0] = '$';
319 0 : strcpy(&retval[1], tmp);
320 0 : rl_completion_append_character = '\0';
321 0 : efree(tmp);
322 : }
323 0 : return retval;
324 : } /* }}} */
325 :
326 : static char *cli_completion_generator_func(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */
327 2 : {
328 : zend_function *func;
329 : char *retval;
330 :
331 2 : retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func TSRMLS_CC);
332 2 : if (retval) {
333 : char *tmp;
334 : int32_t tmp_len, len;
335 1 : UErrorCode status = U_ZERO_ERROR;
336 :
337 1 : rl_completion_append_character = '(';
338 :
339 1 : len = u_strlen((UChar *)func->common.function_name.u);
340 1 : zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(output_encoding_conv)), &tmp, &tmp_len,
341 : (UChar *)func->common.function_name.u, len, &status);
342 :
343 1 : retval = strdup(tmp);
344 1 : efree(tmp);
345 : }
346 :
347 2 : return retval;
348 : } /* }}} */
349 :
350 : static char *cli_completion_generator_class(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */
351 1 : {
352 : zend_class_entry **pce;
353 1 : char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&pce TSRMLS_CC);
354 1 : if (retval) {
355 : char *tmp;
356 : int32_t tmp_len, len;
357 0 : UErrorCode status = U_ZERO_ERROR;
358 :
359 0 : rl_completion_append_character = '\0';
360 :
361 0 : len = u_strlen((UChar *)(*pce)->name.u);
362 0 : zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(output_encoding_conv)), &tmp, &tmp_len,
363 : (UChar *)(*pce)->name.u, len, &status);
364 :
365 0 : retval = strdup(tmp);
366 0 : efree(tmp);
367 : }
368 :
369 1 : return retval;
370 : } /* }}} */
371 :
372 : static char *cli_completion_generator_define(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */
373 1 : {
374 : zend_class_entry **pce;
375 1 : char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce TSRMLS_CC);
376 1 : if (retval) {
377 : char *tmp;
378 : int32_t tmp_len, len;
379 0 : UErrorCode status = U_ZERO_ERROR;
380 :
381 0 : rl_completion_append_character = '\0';
382 :
383 0 : len = u_strlen((UChar *)retval);
384 0 : zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(output_encoding_conv)), &tmp, &tmp_len,
385 : (UChar *)retval, len, &status);
386 :
387 0 : retval = strdup(tmp);
388 0 : efree(tmp);
389 : }
390 :
391 1 : return retval;
392 : } /* }}} */
393 :
394 : static int cli_completion_state;
395 :
396 : static char *cli_completion_generator(const char *text, int index) /* {{{ */
397 2 : {
398 : /*
399 : TODO:
400 : - constants
401 : - maybe array keys
402 : - language constructs and other things outside a hashtable (echo, try, function, class, ...)
403 : - object/class members
404 :
405 : - future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)
406 : */
407 2 : char *retval = NULL;
408 2 : int textlen = strlen(text);
409 : TSRMLS_FETCH();
410 :
411 2 : if (!index) {
412 1 : cli_completion_state = 0;
413 : }
414 2 : if (text[0] == '$') {
415 0 : retval = cli_completion_generator_var(text, textlen, &cli_completion_state TSRMLS_CC);
416 : } else {
417 : char *lc_text, *class_name, *class_name_end;
418 : int class_name_len;
419 2 : zend_class_entry **pce = NULL;
420 :
421 2 : class_name_end = strstr(text, "::");
422 2 : if (class_name_end) {
423 0 : class_name_len = class_name_end - text;
424 0 : class_name = zend_str_tolower_dup(text, class_name_len);
425 0 : class_name[class_name_len] = '\0'; /* not done automatically */
426 0 : if (zend_lookup_class(class_name, class_name_len, &pce TSRMLS_CC)==FAILURE) {
427 0 : efree(class_name);
428 0 : return NULL;
429 : }
430 0 : lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
431 0 : textlen -= (class_name_len + 2);
432 : } else {
433 2 : lc_text = zend_str_tolower_dup(text, textlen);
434 : }
435 :
436 2 : switch (cli_completion_state) {
437 : case 0:
438 : case 1:
439 2 : retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, pce ? &(*pce)->function_table : EG(function_table) TSRMLS_CC);
440 2 : if (retval) {
441 1 : break;
442 : }
443 : case 2:
444 : case 3:
445 1 : retval = cli_completion_generator_define(text, textlen, &cli_completion_state, pce ? &(*pce)->constants_table : EG(zend_constants) TSRMLS_CC);
446 1 : if (retval || pce) {
447 : break;
448 : }
449 : case 4:
450 : case 5:
451 1 : retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state TSRMLS_CC);
452 : break;
453 : default:
454 : break;
455 : }
456 2 : efree(lc_text);
457 2 : if (class_name_end) {
458 0 : efree(class_name);
459 : }
460 2 : if (pce && retval) {
461 0 : char *tmp = malloc(class_name_len + 2 + strlen(retval) + 1);
462 :
463 0 : sprintf(tmp, "%s::%s", (*pce)->name.s, retval);
464 0 : free(retval);
465 0 : retval = tmp;
466 : }
467 : }
468 :
469 2 : return retval;
470 : } /* }}} */
471 :
472 : /* {{{ cli_code_completion
473 : */
474 : char **cli_code_completion(const char *text, int start, int end)
475 1 : {
476 1 : return rl_completion_matches(text, cli_completion_generator);
477 : }
478 : /* }}} */
479 :
480 : #endif /* HAVE_LIBREADLINE || HAVE_LIBEDIT */
481 :
482 : /*
483 : * Local variables:
484 : * tab-width: 4
485 : * c-basic-offset: 4
486 : * End:
487 : * vim600: sw=4 ts=4 fdm=marker
488 : * vim<600: sw=4 ts=4
489 : */
|