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