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_space_types.h"
25 #include "DNA_text_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  */
txtfmt_lua_find_keyword(const char * string)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  */
txtfmt_lua_find_specialvar(const char * string)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 
txtfmt_lua_find_bool(const char * string)139 static int txtfmt_lua_find_bool(const char *string)
140 {
141   int i, len;
142 
143   /* Keep aligned args for readability. */
144   /* clang-format off */
145 
146   if        (STR_LITERAL_STARTSWITH(string, "nil",    len)) { i = len;
147   } else if (STR_LITERAL_STARTSWITH(string, "true",   len)) { i = len;
148   } else if (STR_LITERAL_STARTSWITH(string, "false",  len)) { i = len;
149   } else                                                    { i = 0;
150   }
151 
152   /* clang-format on */
153 
154   /* If next source char is an identifier (eg. 'i' in "Nonetheless") no match */
155   if (i == 0 || text_check_identifier(string[i])) {
156     return -1;
157   }
158   return i;
159 }
160 
txtfmt_lua_format_identifier(const char * str)161 static char txtfmt_lua_format_identifier(const char *str)
162 {
163   char fmt;
164 
165   /* Keep aligned args for readability. */
166   /* clang-format off */
167 
168   if        ((txtfmt_lua_find_specialvar(str))  != -1) { fmt = FMT_TYPE_SPECIAL;
169   } else if ((txtfmt_lua_find_keyword(str))     != -1) { fmt = FMT_TYPE_KEYWORD;
170   } else                                               { fmt = FMT_TYPE_DEFAULT;
171   }
172 
173   /* clang-format on */
174 
175   return fmt;
176 }
177 
txtfmt_lua_format_line(SpaceText * st,TextLine * line,const bool do_next)178 static void txtfmt_lua_format_line(SpaceText *st, TextLine *line, const bool do_next)
179 {
180   FlattenString fs;
181   const char *str;
182   char *fmt;
183   char cont_orig, cont, find, prev = ' ';
184   int len, i;
185 
186   /* Get continuation from previous line */
187   if (line->prev && line->prev->format != NULL) {
188     fmt = line->prev->format;
189     cont = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
190     BLI_assert((FMT_CONT_ALL & cont) == cont);
191   }
192   else {
193     cont = FMT_CONT_NOP;
194   }
195 
196   /* Get original continuation from this line */
197   if (line->format != NULL) {
198     fmt = line->format;
199     cont_orig = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
200     BLI_assert((FMT_CONT_ALL & cont_orig) == cont_orig);
201   }
202   else {
203     cont_orig = 0xFF;
204   }
205 
206   len = flatten_string(st, &fs, line->line);
207   str = fs.buf;
208   if (!text_check_format_len(line, len)) {
209     flatten_string_free(&fs);
210     return;
211   }
212   fmt = line->format;
213 
214   while (*str) {
215     /* Handle escape sequences by skipping both \ and next char */
216     if (*str == '\\') {
217       *fmt = prev;
218       fmt++;
219       str++;
220       if (*str == '\0') {
221         break;
222       }
223       *fmt = prev;
224       fmt++;
225       str += BLI_str_utf8_size_safe(str);
226       continue;
227     }
228     /* Handle continuations */
229     if (cont) {
230       /* Multi-line comments */
231       if (cont & FMT_CONT_COMMENT_C) {
232         if (*str == ']' && *(str + 1) == ']') {
233           *fmt = FMT_TYPE_COMMENT;
234           fmt++;
235           str++;
236           *fmt = FMT_TYPE_COMMENT;
237           cont = FMT_CONT_NOP;
238         }
239         else {
240           *fmt = FMT_TYPE_COMMENT;
241         }
242         /* Handle other comments */
243       }
244       else {
245         find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
246         if (*str == find) {
247           cont = 0;
248         }
249         *fmt = FMT_TYPE_STRING;
250       }
251 
252       str += BLI_str_utf8_size_safe(str) - 1;
253     }
254     /* Not in a string... */
255     else {
256       /* Multi-line comments */
257       if (*str == '-' && *(str + 1) == '-' && *(str + 2) == '[' && *(str + 3) == '[') {
258         cont = FMT_CONT_COMMENT_C;
259         *fmt = FMT_TYPE_COMMENT;
260         fmt++;
261         str++;
262         *fmt = FMT_TYPE_COMMENT;
263         fmt++;
264         str++;
265         *fmt = FMT_TYPE_COMMENT;
266         fmt++;
267         str++;
268         *fmt = FMT_TYPE_COMMENT;
269       }
270       /* Single line comment */
271       else if (*str == '-' && *(str + 1) == '-') {
272         text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - (int)(fmt - line->format));
273       }
274       else if (*str == '"' || *str == '\'') {
275         /* Strings */
276         find = *str;
277         cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
278         *fmt = FMT_TYPE_STRING;
279       }
280       /* Whitespace (all ws. has been converted to spaces) */
281       else if (*str == ' ') {
282         *fmt = FMT_TYPE_WHITESPACE;
283       }
284       /* Numbers (digits not part of an identifier and periods followed by digits) */
285       else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) ||
286                (*str == '.' && text_check_digit(*(str + 1)))) {
287         *fmt = FMT_TYPE_NUMERAL;
288       }
289       /* Booleans */
290       else if (prev != FMT_TYPE_DEFAULT && (i = txtfmt_lua_find_bool(str)) != -1) {
291         if (i > 0) {
292           text_format_fill_ascii(&str, &fmt, FMT_TYPE_NUMERAL, i);
293         }
294         else {
295           str += BLI_str_utf8_size_safe(str) - 1;
296           *fmt = FMT_TYPE_DEFAULT;
297         }
298       }
299       /* Punctuation */
300       else if ((*str != '#') && text_check_delim(*str)) {
301         *fmt = FMT_TYPE_SYMBOL;
302       }
303       /* Identifiers and other text (no previous ws. or delims. so text continues) */
304       else if (prev == FMT_TYPE_DEFAULT) {
305         str += BLI_str_utf8_size_safe(str) - 1;
306         *fmt = FMT_TYPE_DEFAULT;
307       }
308       /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
309       else {
310         /* Keep aligned args for readability. */
311         /* clang-format off */
312 
313         /* Special vars(v) or built-in keywords(b) */
314         /* keep in sync with 'txtfmt_osl_format_identifier()' */
315         if        ((i = txtfmt_lua_find_specialvar(str))   != -1) { prev = FMT_TYPE_SPECIAL;
316         } else if ((i = txtfmt_lua_find_keyword(str))      != -1) { prev = FMT_TYPE_KEYWORD;
317         }
318 
319         /* clang-format on */
320 
321         if (i > 0) {
322           text_format_fill_ascii(&str, &fmt, prev, i);
323         }
324         else {
325           str += BLI_str_utf8_size_safe(str) - 1;
326           *fmt = FMT_TYPE_DEFAULT;
327         }
328       }
329     }
330     prev = *fmt;
331     fmt++;
332     str++;
333   }
334 
335   /* Terminate and add continuation char */
336   *fmt = '\0';
337   fmt++;
338   *fmt = cont;
339 
340   /* If continuation has changed and we're allowed, process the next line */
341   if (cont != cont_orig && do_next && line->next) {
342     txtfmt_lua_format_line(st, line->next, do_next);
343   }
344 
345   flatten_string_free(&fs);
346 }
347 
ED_text_format_register_lua(void)348 void ED_text_format_register_lua(void)
349 {
350   static TextFormatType tft = {NULL};
351   static const char *ext[] = {"lua", NULL};
352 
353   tft.format_identifier = txtfmt_lua_format_identifier;
354   tft.format_line = txtfmt_lua_format_line;
355   tft.ext = ext;
356 
357   ED_text_format_register(&tft);
358 }
359