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
str_startswith_word(const char * str,const char * head)36 STATIC bool str_startswith_word(const char *str, const char *head) {
37 size_t i;
38 for (i = 0; str[i] && head[i]; i++) {
39 if (str[i] != head[i]) {
40 return false;
41 }
42 }
43 return head[i] == '\0' && (str[i] == '\0' || !unichar_isident(str[i]));
44 }
45
mp_repl_continue_with_input(const char * input)46 bool mp_repl_continue_with_input(const char *input) {
47 // check for blank input
48 if (input[0] == '\0') {
49 return false;
50 }
51
52 // check if input starts with a certain keyword
53 bool starts_with_compound_keyword =
54 input[0] == '@'
55 || str_startswith_word(input, "if")
56 || str_startswith_word(input, "while")
57 || str_startswith_word(input, "for")
58 || str_startswith_word(input, "try")
59 || str_startswith_word(input, "with")
60 || str_startswith_word(input, "def")
61 || str_startswith_word(input, "class")
62 #if MICROPY_PY_ASYNC_AWAIT
63 || str_startswith_word(input, "async")
64 #endif
65 ;
66
67 // check for unmatched open bracket, quote or escape quote
68 #define Q_NONE (0)
69 #define Q_1_SINGLE (1)
70 #define Q_1_DOUBLE (2)
71 #define Q_3_SINGLE (3)
72 #define Q_3_DOUBLE (4)
73 int n_paren = 0;
74 int n_brack = 0;
75 int n_brace = 0;
76 int in_quote = Q_NONE;
77 const char *i;
78 for (i = input; *i; i++) {
79 if (*i == '\'') {
80 if ((in_quote == Q_NONE || in_quote == Q_3_SINGLE) && i[1] == '\'' && i[2] == '\'') {
81 i += 2;
82 in_quote = Q_3_SINGLE - in_quote;
83 } else if (in_quote == Q_NONE || in_quote == Q_1_SINGLE) {
84 in_quote = Q_1_SINGLE - in_quote;
85 }
86 } else if (*i == '"') {
87 if ((in_quote == Q_NONE || in_quote == Q_3_DOUBLE) && i[1] == '"' && i[2] == '"') {
88 i += 2;
89 in_quote = Q_3_DOUBLE - in_quote;
90 } else if (in_quote == Q_NONE || in_quote == Q_1_DOUBLE) {
91 in_quote = Q_1_DOUBLE - in_quote;
92 }
93 } else if (*i == '\\' && (i[1] == '\'' || i[1] == '"' || i[1] == '\\')) {
94 if (in_quote != Q_NONE) {
95 i++;
96 }
97 } else if (in_quote == Q_NONE) {
98 switch (*i) {
99 case '(':
100 n_paren += 1;
101 break;
102 case ')':
103 n_paren -= 1;
104 break;
105 case '[':
106 n_brack += 1;
107 break;
108 case ']':
109 n_brack -= 1;
110 break;
111 case '{':
112 n_brace += 1;
113 break;
114 case '}':
115 n_brace -= 1;
116 break;
117 default:
118 break;
119 }
120 }
121 }
122
123 // continue if unmatched 3-quotes
124 if (in_quote == Q_3_SINGLE || in_quote == Q_3_DOUBLE) {
125 return true;
126 }
127
128 // continue if unmatched brackets, but only if not in a 1-quote
129 if ((n_paren > 0 || n_brack > 0 || n_brace > 0) && in_quote == Q_NONE) {
130 return true;
131 }
132
133 // continue if last character was backslash (for line continuation)
134 if (i[-1] == '\\') {
135 return true;
136 }
137
138 // continue if compound keyword and last line was not empty
139 if (starts_with_compound_keyword && i[-1] != '\n') {
140 return true;
141 }
142
143 // otherwise, don't continue
144 return false;
145 }
146
test_qstr(mp_obj_t obj,qstr name)147 STATIC bool test_qstr(mp_obj_t obj, qstr name) {
148 if (obj) {
149 // try object member
150 mp_obj_t dest[2];
151 mp_load_method_protected(obj, name, dest, true);
152 return dest[0] != MP_OBJ_NULL;
153 } else {
154 // try builtin module
155 return mp_map_lookup((mp_map_t *)&mp_builtin_module_map,
156 MP_OBJ_NEW_QSTR(name), MP_MAP_LOOKUP);
157 }
158 }
159
find_completions(const char * s_start,size_t s_len,mp_obj_t obj,size_t * match_len,qstr * q_first,qstr * q_last)160 STATIC const char *find_completions(const char *s_start, size_t s_len,
161 mp_obj_t obj, size_t *match_len, qstr *q_first, qstr *q_last) {
162
163 const char *match_str = NULL;
164 *match_len = 0;
165 *q_first = *q_last = 0;
166 size_t nqstr = QSTR_TOTAL();
167 for (qstr q = MP_QSTR_ + 1; q < nqstr; ++q) {
168 size_t d_len;
169 const char *d_str = (const char *)qstr_data(q, &d_len);
170 // special case; filter out words that begin with underscore
171 // unless there's already a partial match
172 if (s_len == 0 && d_str[0] == '_') {
173 continue;
174 }
175 if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
176 if (test_qstr(obj, q)) {
177 if (match_str == NULL) {
178 match_str = d_str;
179 *match_len = d_len;
180 } else {
181 // search for longest common prefix of match_str and d_str
182 // (assumes these strings are null-terminated)
183 for (size_t j = s_len; j <= *match_len && j <= d_len; ++j) {
184 if (match_str[j] != d_str[j]) {
185 *match_len = j;
186 break;
187 }
188 }
189 }
190 if (*q_first == 0) {
191 *q_first = q;
192 }
193 *q_last = q;
194 }
195 }
196 }
197 return match_str;
198 }
199
print_completions(const mp_print_t * print,const char * s_start,size_t s_len,mp_obj_t obj,qstr q_first,qstr q_last)200 STATIC void print_completions(const mp_print_t *print,
201 const char *s_start, size_t s_len,
202 mp_obj_t obj, qstr q_first, qstr q_last) {
203
204 #define WORD_SLOT_LEN (16)
205 #define MAX_LINE_LEN (4 * WORD_SLOT_LEN)
206
207 int line_len = MAX_LINE_LEN; // force a newline for first word
208 for (qstr q = q_first; q <= q_last; ++q) {
209 size_t d_len;
210 const char *d_str = (const char *)qstr_data(q, &d_len);
211 if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
212 if (test_qstr(obj, q)) {
213 int gap = (line_len + WORD_SLOT_LEN - 1) / WORD_SLOT_LEN * WORD_SLOT_LEN - line_len;
214 if (gap < 2) {
215 gap += WORD_SLOT_LEN;
216 }
217 if (line_len + gap + d_len <= MAX_LINE_LEN) {
218 // TODO optimise printing of gap?
219 for (int j = 0; j < gap; ++j) {
220 mp_print_str(print, " ");
221 }
222 mp_print_str(print, d_str);
223 line_len += gap + d_len;
224 } else {
225 mp_printf(print, "\n%s", d_str);
226 line_len = d_len;
227 }
228 }
229 }
230 }
231 mp_print_str(print, "\n");
232 }
233
mp_repl_autocomplete(const char * str,size_t len,const mp_print_t * print,const char ** compl_str)234 size_t mp_repl_autocomplete(const char *str, size_t len, const mp_print_t *print, const char **compl_str) {
235 // scan backwards to find start of "a.b.c" chain
236 const char *org_str = str;
237 const char *top = str + len;
238 for (const char *s = top; --s >= str;) {
239 if (!(unichar_isalpha(*s) || unichar_isdigit(*s) || *s == '_' || *s == '.')) {
240 ++s;
241 str = s;
242 break;
243 }
244 }
245
246 // begin search in outer global dict which is accessed from __main__
247 mp_obj_t obj = MP_OBJ_FROM_PTR(&mp_module___main__);
248 mp_obj_t dest[2];
249
250 const char *s_start;
251 size_t s_len;
252
253 for (;;) {
254 // get next word in string to complete
255 s_start = str;
256 while (str < top && *str != '.') {
257 ++str;
258 }
259 s_len = str - s_start;
260
261 if (str == top) {
262 // end of string, do completion on this partial name
263 break;
264 }
265
266 // a complete word, lookup in current object
267 qstr q = qstr_find_strn(s_start, s_len);
268 if (q == MP_QSTRnull) {
269 // lookup will fail
270 return 0;
271 }
272 mp_load_method_protected(obj, q, dest, true);
273 obj = dest[0]; // attribute, method, or MP_OBJ_NULL if nothing found
274
275 if (obj == MP_OBJ_NULL) {
276 // lookup failed
277 return 0;
278 }
279
280 // skip '.' to move to next word
281 ++str;
282 }
283
284 // after "import", suggest built-in modules
285 static const char import_str[] = "import ";
286 if (len >= 7 && !memcmp(org_str, import_str, 7)) {
287 obj = MP_OBJ_NULL;
288 }
289
290 // look for matches
291 size_t match_len;
292 qstr q_first, q_last;
293 const char *match_str =
294 find_completions(s_start, s_len, obj, &match_len, &q_first, &q_last);
295
296 // nothing found
297 if (q_first == 0) {
298 // If there're no better alternatives, and if it's first word
299 // in the line, try to complete "import".
300 if (s_start == org_str && s_len > 0 && s_len < sizeof(import_str) - 1) {
301 if (memcmp(s_start, import_str, s_len) == 0) {
302 *compl_str = import_str + s_len;
303 return sizeof(import_str) - 1 - s_len;
304 }
305 }
306 if (q_first == 0) {
307 *compl_str = " ";
308 return s_len ? 0 : 4;
309 }
310 }
311
312 // 1 match found, or multiple matches with a common prefix
313 if (q_first == q_last || match_len > s_len) {
314 *compl_str = match_str + s_len;
315 return match_len - s_len;
316 }
317
318 // multiple matches found, print them out
319 print_completions(print, s_start, s_len, obj, q_first, q_last);
320
321 return (size_t)(-1); // indicate many matches
322 }
323
324 #endif // MICROPY_HELPER_REPL
325