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