PyAPI: update keyword list
[blender.git] / source / blender / editors / space_text / text_format_py.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software Foundation,
15  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16  *
17  * ***** END GPL LICENSE BLOCK *****
18  */
19
20 /** \file blender/editors/space_text/text_format_py.c
21  *  \ingroup sptext
22  */
23
24 #include <string.h>
25
26 #include "BLI_blenlib.h"
27
28 #include "DNA_text_types.h"
29 #include "DNA_space_types.h"
30
31 #include "BKE_text.h"
32
33 #include "text_format.h"
34
35 /* *** Local Functions (for format_line) *** */
36
37 /* Checks the specified source string for a Python built-in function name. This
38  * name must start at the beginning of the source string and must be followed by
39  * a non-identifier (see text_check_identifier(char)) or null character.
40  *
41  * If a built-in function is found, the length of the matching name is returned.
42  * Otherwise, -1 is returned.
43  *
44  * See:
45  * http://docs.python.org/py3k/reference/lexical_analysis.html#keywords
46  */
47
48 static int txtfmt_py_find_builtinfunc(const char *string)
49 {
50         int i, len;
51         /* list is from...
52          * ", ".join(['"%s"' % kw
53          *            for kw in  __import__("keyword").kwlist
54          *            if kw not in {"False", "None", "True", "def", "class"}])
55          *
56          * ... and for this code:
57          * print("\n".join(['else if (STR_LITERAL_STARTSWITH(string, "%s", len)) i = len;' % kw
58          *                  for kw in  __import__("keyword").kwlist
59          *                  if kw not in {"False", "None", "True", "def", "class"}]))
60          */
61
62         if      (STR_LITERAL_STARTSWITH(string, "and",      len)) i = len;
63         else if (STR_LITERAL_STARTSWITH(string, "as",       len)) i = len;
64         else if (STR_LITERAL_STARTSWITH(string, "assert",   len)) i = len;
65         else if (STR_LITERAL_STARTSWITH(string, "async",    len)) i = len;
66         else if (STR_LITERAL_STARTSWITH(string, "await",    len)) i = len;
67         else if (STR_LITERAL_STARTSWITH(string, "break",    len)) i = len;
68         else if (STR_LITERAL_STARTSWITH(string, "continue", len)) i = len;
69         else if (STR_LITERAL_STARTSWITH(string, "del",      len)) i = len;
70         else if (STR_LITERAL_STARTSWITH(string, "elif",     len)) i = len;
71         else if (STR_LITERAL_STARTSWITH(string, "else",     len)) i = len;
72         else if (STR_LITERAL_STARTSWITH(string, "except",   len)) i = len;
73         else if (STR_LITERAL_STARTSWITH(string, "finally",  len)) i = len;
74         else if (STR_LITERAL_STARTSWITH(string, "for",      len)) i = len;
75         else if (STR_LITERAL_STARTSWITH(string, "from",     len)) i = len;
76         else if (STR_LITERAL_STARTSWITH(string, "global",   len)) i = len;
77         else if (STR_LITERAL_STARTSWITH(string, "if",       len)) i = len;
78         else if (STR_LITERAL_STARTSWITH(string, "import",   len)) i = len;
79         else if (STR_LITERAL_STARTSWITH(string, "in",       len)) i = len;
80         else if (STR_LITERAL_STARTSWITH(string, "is",       len)) i = len;
81         else if (STR_LITERAL_STARTSWITH(string, "lambda",   len)) i = len;
82         else if (STR_LITERAL_STARTSWITH(string, "nonlocal", len)) i = len;
83         else if (STR_LITERAL_STARTSWITH(string, "not",      len)) i = len;
84         else if (STR_LITERAL_STARTSWITH(string, "or",       len)) i = len;
85         else if (STR_LITERAL_STARTSWITH(string, "pass",     len)) i = len;
86         else if (STR_LITERAL_STARTSWITH(string, "raise",    len)) i = len;
87         else if (STR_LITERAL_STARTSWITH(string, "return",   len)) i = len;
88         else if (STR_LITERAL_STARTSWITH(string, "try",      len)) i = len;
89         else if (STR_LITERAL_STARTSWITH(string, "while",    len)) i = len;
90         else if (STR_LITERAL_STARTSWITH(string, "with",     len)) i = len;
91         else if (STR_LITERAL_STARTSWITH(string, "yield",    len)) i = len;
92         else                                                      i = 0;
93
94         /* If next source char is an identifier (eg. 'i' in "definite") no match */
95         if (i == 0 || text_check_identifier(string[i]))
96                 return -1;
97         return i;
98 }
99
100 /* Checks the specified source string for a Python special name. This name must
101  * start at the beginning of the source string and must be followed by a non-
102  * identifier (see text_check_identifier(char)) or null character.
103  *
104  * If a special name is found, the length of the matching name is returned.
105  * Otherwise, -1 is returned. */
106
107 static int txtfmt_py_find_specialvar(const char *string)
108 {
109         int i, len;
110
111         if      (STR_LITERAL_STARTSWITH(string, "def", len))   i = len;
112         else if (STR_LITERAL_STARTSWITH(string, "class", len)) i = len;
113         else                                                   i = 0;
114
115         /* If next source char is an identifier (eg. 'i' in "definite") no match */
116         if (i == 0 || text_check_identifier(string[i]))
117                 return -1;
118         return i;
119 }
120
121 static int txtfmt_py_find_decorator(const char *string)
122 {
123         if (string[0] == '@') {
124                 int i = 1;
125                 /* Whitespace is ok '@  foo' */
126                 while (text_check_whitespace(string[i])) {
127                         i++;
128                 }
129                 while (text_check_identifier(string[i])) {
130                         i++;
131                 }
132                 return i;
133         }
134         return -1;
135 }
136
137 static int txtfmt_py_find_bool(const char *string)
138 {
139         int i, len;
140
141         if      (STR_LITERAL_STARTSWITH(string, "None",  len))  i = len;
142         else if (STR_LITERAL_STARTSWITH(string, "True",  len))  i = len;
143         else if (STR_LITERAL_STARTSWITH(string, "False", len))  i = len;
144         else                                                    i = 0;
145
146         /* If next source char is an identifier (eg. 'i' in "Nonetheless") no match */
147         if (i == 0 || text_check_identifier(string[i]))
148                 return -1;
149         return i;
150 }
151
152 static char txtfmt_py_format_identifier(const char *str)
153 {
154         char fmt;
155         if      ((txtfmt_py_find_specialvar(str))   != -1) fmt = FMT_TYPE_SPECIAL;
156         else if ((txtfmt_py_find_builtinfunc(str))  != -1) fmt = FMT_TYPE_KEYWORD;
157         else if ((txtfmt_py_find_decorator(str))    != -1) fmt = FMT_TYPE_RESERVED;
158         else                                               fmt = FMT_TYPE_DEFAULT;
159         return fmt;
160 }
161
162 static void txtfmt_py_format_line(SpaceText *st, TextLine *line, const bool do_next)
163 {
164         FlattenString fs;
165         const char *str;
166         char *fmt;
167         char cont_orig, cont, find, prev = ' ';
168         int len, i;
169
170         /* Get continuation from previous line */
171         if (line->prev && line->prev->format != NULL) {
172                 fmt = line->prev->format;
173                 cont = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
174                 BLI_assert((FMT_CONT_ALL & cont) == cont);
175         }
176         else {
177                 cont = FMT_CONT_NOP;
178         }
179
180         /* Get original continuation from this line */
181         if (line->format != NULL) {
182                 fmt = line->format;
183                 cont_orig = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
184                 BLI_assert((FMT_CONT_ALL & cont_orig) == cont_orig);
185         }
186         else {
187                 cont_orig = 0xFF;
188         }
189
190         len = flatten_string(st, &fs, line->line);
191         str = fs.buf;
192         if (!text_check_format_len(line, len)) {
193                 flatten_string_free(&fs);
194                 return;
195         }
196         fmt = line->format;
197
198         while (*str) {
199                 /* Handle escape sequences by skipping both \ and next char */
200                 if (*str == '\\') {
201                         *fmt = prev; fmt++; str++;
202                         if (*str == '\0') break;
203                         *fmt = prev; fmt++; str += BLI_str_utf8_size_safe(str);
204                         continue;
205                 }
206                 /* Handle continuations */
207                 else if (cont) {
208                         /* Triple strings ("""...""" or '''...''') */
209                         if (cont & FMT_CONT_TRIPLE) {
210                                 find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
211                                 if (*str == find && *(str + 1) == find && *(str + 2) == find) {
212                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
213                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
214                                         cont = FMT_CONT_NOP;
215                                 }
216                                 /* Handle other strings */
217                         }
218                         else {
219                                 find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
220                                 if (*str == find) cont = FMT_CONT_NOP;
221                         }
222
223                         *fmt = FMT_TYPE_STRING;
224                         str += BLI_str_utf8_size_safe(str) - 1;
225                 }
226                 /* Not in a string... */
227                 else {
228                         /* Deal with comments first */
229                         if (*str == '#') {
230                                 /* fill the remaining line */
231                                 text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - (int)(fmt - line->format));
232                         }
233                         else if (*str == '"' || *str == '\'') {
234                                 /* Strings */
235                                 find = *str;
236                                 cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
237                                 if (*(str + 1) == find && *(str + 2) == find) {
238                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
239                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
240                                         cont |= FMT_CONT_TRIPLE;
241                                 }
242                                 *fmt = FMT_TYPE_STRING;
243                         }
244                         /* Whitespace (all ws. has been converted to spaces) */
245                         else if (*str == ' ') {
246                                 *fmt = FMT_TYPE_WHITESPACE;
247                         }
248                         /* Numbers (digits not part of an identifier and periods followed by digits) */
249                         else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) ||
250                                  (*str == '.' && text_check_digit(*(str + 1))))
251                         {
252                                 *fmt = FMT_TYPE_NUMERAL;
253                         }
254                         /* Booleans */
255                         else if (prev != FMT_TYPE_DEFAULT && (i = txtfmt_py_find_bool(str)) != -1) {
256                                 if (i > 0) {
257                                         text_format_fill_ascii(&str, &fmt, FMT_TYPE_NUMERAL, i);
258                                 }
259                                 else {
260                                         str += BLI_str_utf8_size_safe(str) - 1;
261                                         *fmt = FMT_TYPE_DEFAULT;
262                                 }
263                         }
264                         /* Punctuation */
265                         else if ((*str != '@') && text_check_delim(*str)) {
266                                 *fmt = FMT_TYPE_SYMBOL;
267                         }
268                         /* Identifiers and other text (no previous ws. or delims. so text continues) */
269                         else if (prev == FMT_TYPE_DEFAULT) {
270                                 str += BLI_str_utf8_size_safe(str) - 1;
271                                 *fmt = FMT_TYPE_DEFAULT;
272                         }
273                         /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
274                         else {
275                                 /* Special vars(v) or built-in keywords(b) */
276                                 /* keep in sync with 'txtfmt_py_format_identifier()' */
277                                 if      ((i = txtfmt_py_find_specialvar(str))   != -1) prev = FMT_TYPE_SPECIAL;
278                                 else if ((i = txtfmt_py_find_builtinfunc(str))  != -1) prev = FMT_TYPE_KEYWORD;
279                                 else if ((i = txtfmt_py_find_decorator(str))    != -1) prev = FMT_TYPE_DIRECTIVE;
280
281                                 if (i > 0) {
282                                         if (prev == FMT_TYPE_DIRECTIVE) {  /* can contain utf8 */
283                                                 text_format_fill(&str, &fmt, prev, i);
284                                         }
285                                         else {
286                                                 text_format_fill_ascii(&str, &fmt, prev, i);
287                                         }
288                                 }
289                                 else {
290                                         str += BLI_str_utf8_size_safe(str) - 1;
291                                         *fmt = FMT_TYPE_DEFAULT;
292                                 }
293                         }
294                 }
295                 prev = *fmt; fmt++; str++;
296         }
297
298         /* Terminate and add continuation char */
299         *fmt = '\0'; fmt++;
300         *fmt = cont;
301
302         /* If continuation has changed and we're allowed, process the next line */
303         if (cont != cont_orig && do_next && line->next) {
304                 txtfmt_py_format_line(st, line->next, do_next);
305         }
306
307         flatten_string_free(&fs);
308 }
309
310 void ED_text_format_register_py(void)
311 {
312         static TextFormatType tft = {NULL};
313         static const char *ext[] = {"py", NULL};
314
315         tft.format_identifier = txtfmt_py_format_identifier;
316         tft.format_line       = txtfmt_py_format_line;
317         tft.ext = ext;
318
319         ED_text_format_register(&tft);
320 }