Branch data Line data Source code
1 : : /*
2 : : * This file is part of the MicroPython project, http://micropython.org/
3 : : *
4 : : * The MIT License (MIT)
5 : : *
6 : : * Copyright (c) 2013-2015 Damien P. George
7 : : *
8 : : * Permission is hereby granted, free of charge, to any person obtaining a copy
9 : : * of this software and associated documentation files (the "Software"), to deal
10 : : * in the Software without restriction, including without limitation the rights
11 : : * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : : * copies of the Software, and to permit persons to whom the Software is
13 : : * furnished to do so, subject to the following conditions:
14 : : *
15 : : * The above copyright notice and this permission notice shall be included in
16 : : * all copies or substantial portions of the Software.
17 : : *
18 : : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : : * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 : : * THE SOFTWARE.
25 : : */
26 : :
27 : : #include <string.h>
28 : : #include "py/obj.h"
29 : : #include "py/objmodule.h"
30 : : #include "py/runtime.h"
31 : : #include "py/builtin.h"
32 : : #include "py/repl.h"
33 : :
34 : : #if MICROPY_HELPER_REPL
35 : :
36 : : #if MICROPY_PY_SYS_PS1_PS2
37 : 291 : const char *mp_repl_get_psx(unsigned int entry) {
38 [ - + - - : 291 : if (mp_obj_is_str(MP_STATE_VM(sys_mutable)[entry])) {
- - ]
39 : 291 : return mp_obj_str_get_str(MP_STATE_VM(sys_mutable)[entry]);
40 : : } else {
41 : : return "";
42 : : }
43 : : }
44 : : #endif
45 : :
46 : 1802 : static bool str_startswith_word(const char *str, const char *head) {
47 : 1802 : size_t i;
48 [ + - + + ]: 1924 : for (i = 0; str[i] && head[i]; i++) {
49 [ + + ]: 1884 : if (str[i] != head[i]) {
50 : : return false;
51 : : }
52 : : }
53 [ + - + - : 76 : return head[i] == '\0' && (str[i] == '\0' || !unichar_isident(str[i]));
+ + ]
54 : : }
55 : :
56 : 253 : bool mp_repl_continue_with_input(const char *input) {
57 : : // check for blank input
58 [ + - ]: 253 : if (input[0] == '\0') {
59 : : return false;
60 : : }
61 : :
62 : : // check if input starts with a certain keyword
63 : 506 : bool starts_with_compound_keyword =
64 : : input[0] == '@'
65 [ + + ]: 253 : || str_startswith_word(input, "if")
66 [ + - ]: 223 : || str_startswith_word(input, "while")
67 [ + - ]: 223 : || str_startswith_word(input, "for")
68 [ + - ]: 223 : || str_startswith_word(input, "try")
69 [ + - ]: 223 : || str_startswith_word(input, "with")
70 [ + + ]: 223 : || str_startswith_word(input, "def")
71 [ + - ]: 217 : || str_startswith_word(input, "class")
72 : : #if MICROPY_PY_ASYNC_AWAIT
73 [ + - - + ]: 470 : || str_startswith_word(input, "async")
74 : : #endif
75 : : ;
76 : :
77 : : // check for unmatched open bracket, quote or escape quote
78 : : #define Q_NONE (0)
79 : : #define Q_1_SINGLE (1)
80 : : #define Q_1_DOUBLE (2)
81 : : #define Q_3_SINGLE (3)
82 : : #define Q_3_DOUBLE (4)
83 : 253 : int n_paren = 0;
84 : 253 : int n_brack = 0;
85 : 253 : int n_brace = 0;
86 : 253 : int in_quote = Q_NONE;
87 : 253 : const char *i;
88 [ + + ]: 4811 : for (i = input; *i; i++) {
89 [ + + ]: 4558 : if (*i == '\'') {
90 [ + + + + : 64 : if ((in_quote == Q_NONE || in_quote == Q_3_SINGLE) && i[1] == '\'' && i[2] == '\'') {
+ - ]
91 : 6 : i += 2;
92 : 6 : in_quote = Q_3_SINGLE - in_quote;
93 [ + + ]: 58 : } else if (in_quote == Q_NONE || in_quote == Q_1_SINGLE) {
94 : 56 : in_quote = Q_1_SINGLE - in_quote;
95 : : }
96 [ + + ]: 4494 : } else if (*i == '"') {
97 [ + + + + : 44 : if ((in_quote == Q_NONE || in_quote == Q_3_DOUBLE) && i[1] == '"' && i[2] == '"') {
+ - ]
98 : 6 : i += 2;
99 : 6 : in_quote = Q_3_DOUBLE - in_quote;
100 [ + + ]: 38 : } else if (in_quote == Q_NONE || in_quote == Q_1_DOUBLE) {
101 : 36 : in_quote = Q_1_DOUBLE - in_quote;
102 : : }
103 [ + + + + ]: 4450 : } else if (*i == '\\' && (i[1] == '\'' || i[1] == '"' || i[1] == '\\')) {
104 [ + - ]: 16 : if (in_quote != Q_NONE) {
105 : 16 : i++;
106 : : }
107 [ + + ]: 4434 : } else if (in_quote == Q_NONE) {
108 [ + + + + : 4188 : switch (*i) {
+ + + ]
109 : 76 : case '(':
110 : 76 : n_paren += 1;
111 : 76 : break;
112 : 72 : case ')':
113 : 72 : n_paren -= 1;
114 : 72 : break;
115 : 6 : case '[':
116 : 6 : n_brack += 1;
117 : 6 : break;
118 : 4 : case ']':
119 : 4 : n_brack -= 1;
120 : 4 : break;
121 : 4 : case '{':
122 : 4 : n_brace += 1;
123 : 4 : break;
124 : 2 : case '}':
125 : 2 : n_brace -= 1;
126 : 2 : break;
127 : : default:
128 : : break;
129 : : }
130 : : }
131 : : }
132 : :
133 : : // continue if unmatched 3-quotes
134 [ + + ]: 253 : if (in_quote == Q_3_SINGLE || in_quote == Q_3_DOUBLE) {
135 : : return true;
136 : : }
137 : :
138 : : // continue if unmatched brackets, but only if not in a 1-quote
139 [ + + + + : 249 : if ((n_paren > 0 || n_brack > 0 || n_brace > 0) && in_quote == Q_NONE) {
- + ]
140 : : return true;
141 : : }
142 : :
143 : : // continue if last character was backslash (for line continuation)
144 [ + + ]: 241 : if (i[-1] == '\\') {
145 : : return true;
146 : : }
147 : :
148 : : // continue if compound keyword and last line was not empty
149 [ + + + + ]: 239 : if (starts_with_compound_keyword && i[-1] != '\n') {
150 : 28 : return true;
151 : : }
152 : :
153 : : // otherwise, don't continue
154 : : return false;
155 : : }
156 : :
157 : 5318 : static bool test_qstr(mp_obj_t obj, qstr name) {
158 [ + + ]: 5318 : if (obj) {
159 : : // try object member
160 : 2274 : mp_obj_t dest[2];
161 : 2274 : mp_load_method_protected(obj, name, dest, true);
162 : 2274 : return dest[0] != MP_OBJ_NULL;
163 : : } else {
164 : : // try builtin module
165 [ + + + + ]: 6090 : return mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(name), MP_MAP_LOOKUP) ||
166 : 2964 : mp_map_lookup((mp_map_t *)&mp_builtin_extensible_module_map, MP_OBJ_NEW_QSTR(name), MP_MAP_LOOKUP);
167 : : }
168 : : }
169 : :
170 : 22 : static const char *find_completions(const char *s_start, size_t s_len,
171 : : mp_obj_t obj, size_t *match_len, qstr *q_first, qstr *q_last) {
172 : :
173 : 22 : const char *match_str = NULL;
174 : 22 : *match_len = 0;
175 : 22 : *q_first = *q_last = 0;
176 : 22 : size_t nqstr = QSTR_TOTAL();
177 [ + + ]: 18580 : for (qstr q = MP_QSTR_ + 1; q < nqstr; ++q) {
178 : 18558 : size_t d_len;
179 : 18558 : const char *d_str = (const char *)qstr_data(q, &d_len);
180 : : // special case; filter out words that begin with underscore
181 : : // unless there's already a partial match
182 [ + + + + ]: 18558 : if (s_len == 0 && d_str[0] == '_') {
183 : 424 : continue;
184 : : }
185 [ + + + + ]: 18134 : if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
186 [ + + ]: 3088 : if (test_qstr(obj, q)) {
187 [ + + ]: 132 : if (match_str == NULL) {
188 : 16 : match_str = d_str;
189 : 16 : *match_len = d_len;
190 : : } else {
191 : : // search for longest common prefix of match_str and d_str
192 : : // (assumes these strings are null-terminated)
193 [ + + + - ]: 122 : for (size_t j = s_len; j <= *match_len && j <= d_len; ++j) {
194 [ + + ]: 118 : if (match_str[j] != d_str[j]) {
195 : 112 : *match_len = j;
196 : 112 : break;
197 : : }
198 : : }
199 : : }
200 [ + + ]: 132 : if (*q_first == 0) {
201 : 16 : *q_first = q;
202 : : }
203 : 132 : *q_last = q;
204 : : }
205 : : }
206 : : }
207 : 22 : return match_str;
208 : : }
209 : :
210 : 6 : static void print_completions(const mp_print_t *print,
211 : : const char *s_start, size_t s_len,
212 : : mp_obj_t obj, qstr q_first, qstr q_last) {
213 : :
214 : : #define WORD_SLOT_LEN (16)
215 : : #define MAX_LINE_LEN (4 * WORD_SLOT_LEN)
216 : :
217 : 6 : int line_len = MAX_LINE_LEN; // force a newline for first word
218 [ + + ]: 3206 : for (qstr q = q_first; q <= q_last; ++q) {
219 : 3200 : size_t d_len;
220 : 3200 : const char *d_str = (const char *)qstr_data(q, &d_len);
221 [ + - + + ]: 3200 : if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
222 [ + + ]: 2230 : if (test_qstr(obj, q)) {
223 : 126 : int gap = (line_len + WORD_SLOT_LEN - 1) / WORD_SLOT_LEN * WORD_SLOT_LEN - line_len;
224 [ + + ]: 126 : if (gap < 2) {
225 : 10 : gap += WORD_SLOT_LEN;
226 : : }
227 [ + + ]: 126 : if (line_len + gap + d_len <= MAX_LINE_LEN) {
228 [ + + ]: 1002 : for (int j = 0; j < gap; ++j) {
229 : 910 : mp_print_str(print, " ");
230 : : }
231 : 92 : mp_print_str(print, d_str);
232 : 92 : line_len += gap + d_len;
233 : : } else {
234 : 34 : mp_printf(print, "\n%s", d_str);
235 : 34 : line_len = d_len;
236 : : }
237 : : }
238 : : }
239 : : }
240 : 6 : mp_print_str(print, "\n");
241 : 6 : }
242 : :
243 : 26 : size_t mp_repl_autocomplete(const char *str, size_t len, const mp_print_t *print, const char **compl_str) {
244 : : // scan backwards to find start of "a.b.c" chain
245 : 26 : const char *org_str = str;
246 : 26 : const char *top = str + len;
247 [ + + ]: 146 : for (const char *s = top; --s >= str;) {
248 [ + + + - : 128 : if (!(unichar_isalpha(*s) || unichar_isdigit(*s) || *s == '_' || *s == '.')) {
+ + + + ]
249 : 34 : ++s;
250 : : str = s;
251 : : break;
252 : : }
253 : : }
254 : :
255 : : // begin search in outer global dict which is accessed from __main__
256 : : mp_obj_t obj = MP_OBJ_FROM_PTR(&mp_module___main__);
257 : 34 : mp_obj_t dest[2];
258 : :
259 : 34 : const char *s_start;
260 : 34 : size_t s_len;
261 : :
262 : 42 : for (;;) {
263 : : // get next word in string to complete
264 : 34 : s_start = str;
265 [ + + + + ]: 142 : while (str < top && *str != '.') {
266 : 108 : ++str;
267 : : }
268 : 34 : s_len = str - s_start;
269 : :
270 [ + + ]: 34 : if (str == top) {
271 : : // end of string, do completion on this partial name
272 : : break;
273 : : }
274 : :
275 : : // a complete word, lookup in current object
276 : 12 : qstr q = qstr_find_strn(s_start, s_len);
277 [ + + ]: 12 : if (q == MP_QSTRnull) {
278 : : // lookup will fail
279 : : return 0;
280 : : }
281 : 10 : mp_load_method_protected(obj, q, dest, true);
282 : 10 : obj = dest[0]; // attribute, method, or MP_OBJ_NULL if nothing found
283 : :
284 [ + + ]: 10 : if (obj == MP_OBJ_NULL) {
285 : : // lookup failed
286 : : return 0;
287 : : }
288 : :
289 : : // skip '.' to move to next word
290 : 8 : ++str;
291 : : }
292 : :
293 : : // after "import", suggest built-in modules
294 : 22 : static const char import_str[] = "import ";
295 [ + + + + ]: 22 : if (len >= 7 && !memcmp(org_str, import_str, 7)) {
296 : 6 : obj = MP_OBJ_NULL;
297 : : }
298 : :
299 : : // look for matches
300 : 22 : size_t match_len;
301 : 22 : qstr q_first, q_last;
302 : 22 : const char *match_str =
303 : 22 : find_completions(s_start, s_len, obj, &match_len, &q_first, &q_last);
304 : :
305 : : // nothing found
306 [ + + ]: 22 : if (q_first == 0) {
307 : : // If there're no better alternatives, and if it's first word
308 : : // in the line, try to complete "import".
309 [ + - + + ]: 6 : if (s_start == org_str && s_len > 0 && s_len < sizeof(import_str) - 1) {
310 [ + - ]: 4 : if (memcmp(s_start, import_str, s_len) == 0) {
311 : 4 : *compl_str = import_str + s_len;
312 : 4 : return sizeof(import_str) - 1 - s_len;
313 : : }
314 : : }
315 : : return 0;
316 : : }
317 : :
318 : : // 1 match found, or multiple matches with a common prefix
319 [ + + - + ]: 16 : if (q_first == q_last || match_len > s_len) {
320 : 10 : *compl_str = match_str + s_len;
321 : 10 : return match_len - s_len;
322 : : }
323 : :
324 : : // multiple matches found, print them out
325 : 6 : print_completions(print, s_start, s_len, obj, q_first, q_last);
326 : :
327 : 6 : return (size_t)(-1); // indicate many matches
328 : : }
329 : :
330 : : #endif // MICROPY_HELPER_REPL
|