Cleanup: style, use braces for editors
[blender.git] / source / blender / editors / space_text / text_format_lua.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * GNU General Public License for more details.
10  *
11  * You should have received a copy of the GNU General Public License
12  * along with this program; if not, write to the Free Software Foundation,
13  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14  */
15
16 /** \file
17  * \ingroup sptext
18  */
19
20 #include <string.h>
21
22 #include "BLI_blenlib.h"
23
24 #include "DNA_text_types.h"
25 #include "DNA_space_types.h"
26
27 #include "BKE_text.h"
28
29 #include "text_format.h"
30
31 /* *** Lua Keywords (for format_line) *** */
32
33 /**
34  * Checks the specified source string for a Lua keyword (minus boolean & 'nil').
35  * This name must start at the beginning of the source string and must be
36  * followed by a non-identifier (see #text_check_identifier(char)) or null char.
37  *
38  * If a keyword is found, the length of the matching word is returned.
39  * Otherwise, -1 is returned.
40  *
41  * See:
42  * http://www.lua.org/manual/5.1/manual.html#2.1
43  */
44 static int txtfmt_lua_find_keyword(const char *string)
45 {
46   int i, len;
47
48   /* Keep aligned args for readability. */
49   /* clang-format off */
50
51   if      (STR_LITERAL_STARTSWITH(string, "and",      len)) { i = len;
52   } else if (STR_LITERAL_STARTSWITH(string, "break",    len)) { i = len;
53   } else if (STR_LITERAL_STARTSWITH(string, "do",       len)) { i = len;
54   } else if (STR_LITERAL_STARTSWITH(string, "else",     len)) { i = len;
55   } else if (STR_LITERAL_STARTSWITH(string, "elseif",   len)) { i = len;
56   } else if (STR_LITERAL_STARTSWITH(string, "end",      len)) { i = len;
57   } else if (STR_LITERAL_STARTSWITH(string, "for",      len)) { i = len;
58   } else if (STR_LITERAL_STARTSWITH(string, "function", len)) { i = len;
59   } else if (STR_LITERAL_STARTSWITH(string, "if",       len)) { i = len;
60   } else if (STR_LITERAL_STARTSWITH(string, "in",       len)) { i = len;
61   } else if (STR_LITERAL_STARTSWITH(string, "local",    len)) { i = len;
62   } else if (STR_LITERAL_STARTSWITH(string, "not",      len)) { i = len;
63   } else if (STR_LITERAL_STARTSWITH(string, "or",       len)) { i = len;
64   } else if (STR_LITERAL_STARTSWITH(string, "repeat",   len)) { i = len;
65   } else if (STR_LITERAL_STARTSWITH(string, "return",   len)) { i = len;
66   } else if (STR_LITERAL_STARTSWITH(string, "then",     len)) { i = len;
67   } else if (STR_LITERAL_STARTSWITH(string, "until",    len)) { i = len;
68   } else if (STR_LITERAL_STARTSWITH(string, "while",    len)) { i = len;
69   } else {                                                      i = 0;
70 }
71
72   /* clang-format on */
73
74   /* If next source char is an identifier (eg. 'i' in "definite") no match */
75   if (i == 0 || text_check_identifier(string[i])) {
76     return -1;
77   }
78   return i;
79 }
80
81 /**
82  * Checks the specified source string for a Lua special name/function. This
83  * name must start at the beginning of the source string and must be followed
84  * by a non-identifier (see *text_check_identifier(char)) or null character.
85  *
86  * If a special name is found, the length of the matching name is returned.
87  * Otherwise, -1 is returned.
88  *
89  * See:
90  * http://www.lua.org/manual/5.1/manual.html#5.1
91  */
92 static int txtfmt_lua_find_specialvar(const char *string)
93 {
94   int i, len;
95
96   /* Keep aligned args for readability. */
97   /* clang-format off */
98
99   if      (STR_LITERAL_STARTSWITH(string, "assert",           len)) {   i = len;
100   } else if (STR_LITERAL_STARTSWITH(string, "collectgarbage",   len)) {   i = len;
101   } else if (STR_LITERAL_STARTSWITH(string, "dofile",           len)) {   i = len;
102   } else if (STR_LITERAL_STARTSWITH(string, "error",            len)) {   i = len;
103   } else if (STR_LITERAL_STARTSWITH(string, "_G",               len)) {   i = len;
104   } else if (STR_LITERAL_STARTSWITH(string, "getfenv",          len)) {   i = len;
105   } else if (STR_LITERAL_STARTSWITH(string, "getmetatable",     len)) {   i = len;
106   } else if (STR_LITERAL_STARTSWITH(string, "__index",          len)) {   i = len;
107   } else if (STR_LITERAL_STARTSWITH(string, "ipairs",           len)) {   i = len;
108   } else if (STR_LITERAL_STARTSWITH(string, "load",             len)) {   i = len;
109   } else if (STR_LITERAL_STARTSWITH(string, "loadfile",         len)) {   i = len;
110   } else if (STR_LITERAL_STARTSWITH(string, "loadstring",       len)) {   i = len;
111   } else if (STR_LITERAL_STARTSWITH(string, "next",             len)) {   i = len;
112   } else if (STR_LITERAL_STARTSWITH(string, "pairs",            len)) {   i = len;
113   } else if (STR_LITERAL_STARTSWITH(string, "pcall",            len)) {   i = len;
114   } else if (STR_LITERAL_STARTSWITH(string, "print",            len)) {   i = len;
115   } else if (STR_LITERAL_STARTSWITH(string, "rawequal",         len)) {   i = len;
116   } else if (STR_LITERAL_STARTSWITH(string, "rawget",           len)) {   i = len;
117   } else if (STR_LITERAL_STARTSWITH(string, "rawset",           len)) {   i = len;
118   } else if (STR_LITERAL_STARTSWITH(string, "select",           len)) {   i = len;
119   } else if (STR_LITERAL_STARTSWITH(string, "setfenv",          len)) {   i = len;
120   } else if (STR_LITERAL_STARTSWITH(string, "setmetatable",     len)) {   i = len;
121   } else if (STR_LITERAL_STARTSWITH(string, "tonumber",         len)) {   i = len;
122   } else if (STR_LITERAL_STARTSWITH(string, "tostring",         len)) {   i = len;
123   } else if (STR_LITERAL_STARTSWITH(string, "type",             len)) {   i = len;
124   } else if (STR_LITERAL_STARTSWITH(string, "unpack",           len)) {   i = len;
125   } else if (STR_LITERAL_STARTSWITH(string, "_VERSION",         len)) {   i = len;
126   } else if (STR_LITERAL_STARTSWITH(string, "xpcall",           len)) {   i = len;
127   } else {                                                i = 0;
128 }
129
130   /* clang-format on */
131
132   /* If next source char is an identifier (eg. 'i' in "definite") no match */
133   if (i == 0 || text_check_identifier(string[i])) {
134     return -1;
135   }
136   return i;
137 }
138
139 static int txtfmt_lua_find_bool(const char *string)
140 {
141   int i, len;
142
143   if (STR_LITERAL_STARTSWITH(string, "nil", len)) {
144     i = len;
145   }
146   else if (STR_LITERAL_STARTSWITH(string, "true", len)) {
147     i = len;
148   }
149   else if (STR_LITERAL_STARTSWITH(string, "false", len)) {
150     i = len;
151   }
152   else {
153     i = 0;
154   }
155
156   /* clang-format on */
157
158   /* If next source char is an identifier (eg. 'i' in "Nonetheless") no match */
159   if (i == 0 || text_check_identifier(string[i])) {
160     return -1;
161   }
162   return i;
163 }
164
165 static char txtfmt_lua_format_identifier(const char *str)
166 {
167   char fmt;
168
169   /* Keep aligned args for readability. */
170   /* clang-format off */
171
172   if      ((txtfmt_lua_find_specialvar(str))  != -1) { fmt = FMT_TYPE_SPECIAL;
173   } else if ((txtfmt_lua_find_keyword(str))     != -1) { fmt = FMT_TYPE_KEYWORD;
174   } else {                                               fmt = FMT_TYPE_DEFAULT;
175 }
176
177   /* clang-format on */
178
179   return fmt;
180 }
181
182 static void txtfmt_lua_format_line(SpaceText *st, TextLine *line, const bool do_next)
183 {
184   FlattenString fs;
185   const char *str;
186   char *fmt;
187   char cont_orig, cont, find, prev = ' ';
188   int len, i;
189
190   /* Get continuation from previous line */
191   if (line->prev && line->prev->format != NULL) {
192     fmt = line->prev->format;
193     cont = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
194     BLI_assert((FMT_CONT_ALL & cont) == cont);
195   }
196   else {
197     cont = FMT_CONT_NOP;
198   }
199
200   /* Get original continuation from this line */
201   if (line->format != NULL) {
202     fmt = line->format;
203     cont_orig = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
204     BLI_assert((FMT_CONT_ALL & cont_orig) == cont_orig);
205   }
206   else {
207     cont_orig = 0xFF;
208   }
209
210   len = flatten_string(st, &fs, line->line);
211   str = fs.buf;
212   if (!text_check_format_len(line, len)) {
213     flatten_string_free(&fs);
214     return;
215   }
216   fmt = line->format;
217
218   while (*str) {
219     /* Handle escape sequences by skipping both \ and next char */
220     if (*str == '\\') {
221       *fmt = prev;
222       fmt++;
223       str++;
224       if (*str == '\0') {
225         break;
226       }
227       *fmt = prev;
228       fmt++;
229       str += BLI_str_utf8_size_safe(str);
230       continue;
231     }
232     /* Handle continuations */
233     else if (cont) {
234       /* Multi-line comments */
235       if (cont & FMT_CONT_COMMENT_C) {
236         if (*str == ']' && *(str + 1) == ']') {
237           *fmt = FMT_TYPE_COMMENT;
238           fmt++;
239           str++;
240           *fmt = FMT_TYPE_COMMENT;
241           cont = FMT_CONT_NOP;
242         }
243         else {
244           *fmt = FMT_TYPE_COMMENT;
245         }
246         /* Handle other comments */
247       }
248       else {
249         find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
250         if (*str == find) {
251           cont = 0;
252         }
253         *fmt = FMT_TYPE_STRING;
254       }
255
256       str += BLI_str_utf8_size_safe(str) - 1;
257     }
258     /* Not in a string... */
259     else {
260       /* Multi-line comments */
261       if (*str == '-' && *(str + 1) == '-' && *(str + 2) == '[' && *(str + 3) == '[') {
262         cont = FMT_CONT_COMMENT_C;
263         *fmt = FMT_TYPE_COMMENT;
264         fmt++;
265         str++;
266         *fmt = FMT_TYPE_COMMENT;
267         fmt++;
268         str++;
269         *fmt = FMT_TYPE_COMMENT;
270         fmt++;
271         str++;
272         *fmt = FMT_TYPE_COMMENT;
273       }
274       /* Single line comment */
275       else if (*str == '-' && *(str + 1) == '-') {
276         text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - (int)(fmt - line->format));
277       }
278       else if (*str == '"' || *str == '\'') {
279         /* Strings */
280         find = *str;
281         cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
282         *fmt = FMT_TYPE_STRING;
283       }
284       /* Whitespace (all ws. has been converted to spaces) */
285       else if (*str == ' ') {
286         *fmt = FMT_TYPE_WHITESPACE;
287       }
288       /* Numbers (digits not part of an identifier and periods followed by digits) */
289       else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) ||
290                (*str == '.' && text_check_digit(*(str + 1)))) {
291         *fmt = FMT_TYPE_NUMERAL;
292       }
293       /* Booleans */
294       else if (prev != FMT_TYPE_DEFAULT && (i = txtfmt_lua_find_bool(str)) != -1) {
295         if (i > 0) {
296           text_format_fill_ascii(&str, &fmt, FMT_TYPE_NUMERAL, i);
297         }
298         else {
299           str += BLI_str_utf8_size_safe(str) - 1;
300           *fmt = FMT_TYPE_DEFAULT;
301         }
302       }
303       /* Punctuation */
304       else if ((*str != '#') && text_check_delim(*str)) {
305         *fmt = FMT_TYPE_SYMBOL;
306       }
307       /* Identifiers and other text (no previous ws. or delims. so text continues) */
308       else if (prev == FMT_TYPE_DEFAULT) {
309         str += BLI_str_utf8_size_safe(str) - 1;
310         *fmt = FMT_TYPE_DEFAULT;
311       }
312       /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
313       else {
314         /* Keep aligned args for readability. */
315         /* clang-format off */
316
317         /* Special vars(v) or built-in keywords(b) */
318         /* keep in sync with 'txtfmt_osl_format_identifier()' */
319         if      ((i = txtfmt_lua_find_specialvar(str))   != -1) { prev = FMT_TYPE_SPECIAL;
320         } else if ((i = txtfmt_lua_find_keyword(str))      != -1) { prev = FMT_TYPE_KEYWORD;
321 }
322
323         /* clang-format on */
324
325         if (i > 0) {
326           text_format_fill_ascii(&str, &fmt, prev, i);
327         }
328         else {
329           str += BLI_str_utf8_size_safe(str) - 1;
330           *fmt = FMT_TYPE_DEFAULT;
331         }
332       }
333     }
334     prev = *fmt;
335     fmt++;
336     str++;
337   }
338
339   /* Terminate and add continuation char */
340   *fmt = '\0';
341   fmt++;
342   *fmt = cont;
343
344   /* If continuation has changed and we're allowed, process the next line */
345   if (cont != cont_orig && do_next && line->next) {
346     txtfmt_lua_format_line(st, line->next, do_next);
347   }
348
349   flatten_string_free(&fs);
350 }
351
352 void ED_text_format_register_lua(void)
353 {
354   static TextFormatType tft = {NULL};
355   static const char *ext[] = {"lua", NULL};
356
357   tft.format_identifier = txtfmt_lua_format_identifier;
358   tft.format_line = txtfmt_lua_format_line;
359   tft.ext = ext;
360
361   ED_text_format_register(&tft);
362 }