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