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 /* *** Local Functions (for format_line) *** */
32 
txtfmt_osl_find_builtinfunc(const char * string)33 static int txtfmt_osl_find_builtinfunc(const char *string)
34 {
35   int i, len;
36 
37   /* Keep aligned args for readability. */
38   /* clang-format off */
39 
40   /* list is from
41    * https://github.com/imageworks/OpenShadingLanguage/raw/master/src/doc/osl-languagespec.pdf
42    */
43   if        (STR_LITERAL_STARTSWITH(string, "break",        len)) { i = len;
44   } else if (STR_LITERAL_STARTSWITH(string, "closure",      len)) { i = len;
45   } else if (STR_LITERAL_STARTSWITH(string, "color",        len)) { i = len;
46   } else if (STR_LITERAL_STARTSWITH(string, "continue",     len)) { i = len;
47   } else if (STR_LITERAL_STARTSWITH(string, "do",           len)) { i = len;
48   } else if (STR_LITERAL_STARTSWITH(string, "else",         len)) { i = len;
49   } else if (STR_LITERAL_STARTSWITH(string, "emit",         len)) { i = len;
50   } else if (STR_LITERAL_STARTSWITH(string, "float",        len)) { i = len;
51   } else if (STR_LITERAL_STARTSWITH(string, "for",          len)) { i = len;
52   } else if (STR_LITERAL_STARTSWITH(string, "if",           len)) { i = len;
53   } else if (STR_LITERAL_STARTSWITH(string, "illuminance",  len)) { i = len;
54   } else if (STR_LITERAL_STARTSWITH(string, "illuminate",   len)) { i = len;
55   } else if (STR_LITERAL_STARTSWITH(string, "int",          len)) { i = len;
56   } else if (STR_LITERAL_STARTSWITH(string, "matrix",       len)) { i = len;
57   } else if (STR_LITERAL_STARTSWITH(string, "normal",       len)) { i = len;
58   } else if (STR_LITERAL_STARTSWITH(string, "output",       len)) { i = len;
59   } else if (STR_LITERAL_STARTSWITH(string, "point",        len)) { i = len;
60   } else if (STR_LITERAL_STARTSWITH(string, "public",       len)) { i = len;
61   } else if (STR_LITERAL_STARTSWITH(string, "return",       len)) { i = len;
62   } else if (STR_LITERAL_STARTSWITH(string, "string",       len)) { i = len;
63   } else if (STR_LITERAL_STARTSWITH(string, "struct",       len)) { i = len;
64   } else if (STR_LITERAL_STARTSWITH(string, "vector",       len)) { i = len;
65   } else if (STR_LITERAL_STARTSWITH(string, "void",         len)) { i = len;
66   } else if (STR_LITERAL_STARTSWITH(string, "while",        len)) { i = len;
67   } else                                                          { i = 0;
68   }
69 
70   /* clang-format on */
71 
72   /* If next source char is an identifier (eg. 'i' in "definite") no match */
73   if (i == 0 || text_check_identifier(string[i])) {
74     return -1;
75   }
76   return i;
77 }
78 
txtfmt_osl_find_reserved(const char * string)79 static int txtfmt_osl_find_reserved(const char *string)
80 {
81   int i, len;
82 
83   /* Keep aligned args for readability. */
84   /* clang-format off */
85 
86   /* list is from...
87    * https://github.com/imageworks/OpenShadingLanguage/raw/master/src/doc/osl-languagespec.pdf
88    */
89   if        (STR_LITERAL_STARTSWITH(string, "bool",         len)) { i = len;
90   } else if (STR_LITERAL_STARTSWITH(string, "case",         len)) { i = len;
91   } else if (STR_LITERAL_STARTSWITH(string, "catch",        len)) { i = len;
92   } else if (STR_LITERAL_STARTSWITH(string, "char",         len)) { i = len;
93   } else if (STR_LITERAL_STARTSWITH(string, "const",        len)) { i = len;
94   } else if (STR_LITERAL_STARTSWITH(string, "delete",       len)) { i = len;
95   } else if (STR_LITERAL_STARTSWITH(string, "default",      len)) { i = len;
96   } else if (STR_LITERAL_STARTSWITH(string, "double",       len)) { i = len;
97   } else if (STR_LITERAL_STARTSWITH(string, "enum",         len)) { i = len;
98   } else if (STR_LITERAL_STARTSWITH(string, "extern",       len)) { i = len;
99   } else if (STR_LITERAL_STARTSWITH(string, "false",        len)) { i = len;
100   } else if (STR_LITERAL_STARTSWITH(string, "friend",       len)) { i = len;
101   } else if (STR_LITERAL_STARTSWITH(string, "goto",         len)) { i = len;
102   } else if (STR_LITERAL_STARTSWITH(string, "inline",       len)) { i = len;
103   } else if (STR_LITERAL_STARTSWITH(string, "long",         len)) { i = len;
104   } else if (STR_LITERAL_STARTSWITH(string, "new",          len)) { i = len;
105   } else if (STR_LITERAL_STARTSWITH(string, "operator",     len)) { i = len;
106   } else if (STR_LITERAL_STARTSWITH(string, "private",      len)) { i = len;
107   } else if (STR_LITERAL_STARTSWITH(string, "protected",    len)) { i = len;
108   } else if (STR_LITERAL_STARTSWITH(string, "short",        len)) { i = len;
109   } else if (STR_LITERAL_STARTSWITH(string, "signed",       len)) { i = len;
110   } else if (STR_LITERAL_STARTSWITH(string, "sizeof",       len)) { i = len;
111   } else if (STR_LITERAL_STARTSWITH(string, "static",       len)) { i = len;
112   } else if (STR_LITERAL_STARTSWITH(string, "switch",       len)) { i = len;
113   } else if (STR_LITERAL_STARTSWITH(string, "template",     len)) { i = len;
114   } else if (STR_LITERAL_STARTSWITH(string, "this",         len)) { i = len;
115   } else if (STR_LITERAL_STARTSWITH(string, "throw",        len)) { i = len;
116   } else if (STR_LITERAL_STARTSWITH(string, "true",         len)) { i = len;
117   } else if (STR_LITERAL_STARTSWITH(string, "try",          len)) { i = len;
118   } else if (STR_LITERAL_STARTSWITH(string, "typedef",      len)) { i = len;
119   } else if (STR_LITERAL_STARTSWITH(string, "uniform",      len)) { i = len;
120   } else if (STR_LITERAL_STARTSWITH(string, "union",        len)) { i = len;
121   } else if (STR_LITERAL_STARTSWITH(string, "unsigned",     len)) { i = len;
122   } else if (STR_LITERAL_STARTSWITH(string, "varying",      len)) { i = len;
123   } else if (STR_LITERAL_STARTSWITH(string, "virtual",      len)) { i = len;
124   } else if (STR_LITERAL_STARTSWITH(string, "volatile",     len)) { i = len;
125   } else                                                          { i = 0;
126   }
127 
128   /* clang-format on */
129 
130   /* If next source char is an identifier (eg. 'i' in "definite") no match */
131   if (i == 0 || text_check_identifier(string[i])) {
132     return -1;
133   }
134   return i;
135 }
136 
137 /* Checks the specified source string for a OSL special name. This name must
138  * start at the beginning of the source string and must be followed by a non-
139  * identifier (see text_check_identifier(char)) or null character.
140  *
141  * If a special name is found, the length of the matching name is returned.
142  * Otherwise, -1 is returned. */
143 
txtfmt_osl_find_specialvar(const char * string)144 static int txtfmt_osl_find_specialvar(const char *string)
145 {
146   int i, len;
147 
148   /* Keep aligned args for readability. */
149   /* clang-format off */
150 
151   /* OSL shader types */
152   if        (STR_LITERAL_STARTSWITH(string, "shader",       len)) { i = len;
153   } else if (STR_LITERAL_STARTSWITH(string, "surface",      len)) { i = len;
154   } else if (STR_LITERAL_STARTSWITH(string, "volume",       len)) { i = len;
155   } else if (STR_LITERAL_STARTSWITH(string, "displacement", len)) { i = len;
156   } else                                                          { i = 0;
157   }
158 
159   /* clang-format on */
160 
161   /* If next source char is an identifier (eg. 'i' in "definite") no match */
162   if (i == 0 || text_check_identifier(string[i])) {
163     return -1;
164   }
165   return i;
166 }
167 
168 /* matches py 'txtfmt_osl_find_decorator' */
txtfmt_osl_find_preprocessor(const char * string)169 static int txtfmt_osl_find_preprocessor(const char *string)
170 {
171   if (string[0] == '#') {
172     int i = 1;
173     /* Whitespace is ok '#  foo' */
174     while (text_check_whitespace(string[i])) {
175       i++;
176     }
177     while (text_check_identifier(string[i])) {
178       i++;
179     }
180     return i;
181   }
182   return -1;
183 }
184 
txtfmt_osl_format_identifier(const char * str)185 static char txtfmt_osl_format_identifier(const char *str)
186 {
187   char fmt;
188 
189   /* Keep aligned args for readability. */
190   /* clang-format off */
191 
192   if        ((txtfmt_osl_find_specialvar(str))   != -1) { fmt = FMT_TYPE_SPECIAL;
193   } else if ((txtfmt_osl_find_builtinfunc(str))  != -1) { fmt = FMT_TYPE_KEYWORD;
194   } else if ((txtfmt_osl_find_reserved(str))     != -1) { fmt = FMT_TYPE_RESERVED;
195   } else if ((txtfmt_osl_find_preprocessor(str)) != -1) { fmt = FMT_TYPE_DIRECTIVE;
196   } else                                                { fmt = FMT_TYPE_DEFAULT;
197   }
198 
199   /* clang-format on */
200 
201   return fmt;
202 }
203 
txtfmt_osl_format_line(SpaceText * st,TextLine * line,const bool do_next)204 static void txtfmt_osl_format_line(SpaceText *st, TextLine *line, const bool do_next)
205 {
206   FlattenString fs;
207   const char *str;
208   char *fmt;
209   char cont_orig, cont, find, prev = ' ';
210   int len, i;
211 
212   /* Get continuation from previous line */
213   if (line->prev && line->prev->format != NULL) {
214     fmt = line->prev->format;
215     cont = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
216     BLI_assert((FMT_CONT_ALL & cont) == cont);
217   }
218   else {
219     cont = FMT_CONT_NOP;
220   }
221 
222   /* Get original continuation from this line */
223   if (line->format != NULL) {
224     fmt = line->format;
225     cont_orig = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
226     BLI_assert((FMT_CONT_ALL & cont_orig) == cont_orig);
227   }
228   else {
229     cont_orig = 0xFF;
230   }
231 
232   len = flatten_string(st, &fs, line->line);
233   str = fs.buf;
234   if (!text_check_format_len(line, len)) {
235     flatten_string_free(&fs);
236     return;
237   }
238   fmt = line->format;
239 
240   while (*str) {
241     /* Handle escape sequences by skipping both \ and next char */
242     if (*str == '\\') {
243       *fmt = prev;
244       fmt++;
245       str++;
246       if (*str == '\0') {
247         break;
248       }
249       *fmt = prev;
250       fmt++;
251       str += BLI_str_utf8_size_safe(str);
252       continue;
253     }
254     /* Handle continuations */
255     if (cont) {
256       /* C-Style comments */
257       if (cont & FMT_CONT_COMMENT_C) {
258         if (*str == '*' && *(str + 1) == '/') {
259           *fmt = FMT_TYPE_COMMENT;
260           fmt++;
261           str++;
262           *fmt = FMT_TYPE_COMMENT;
263           cont = FMT_CONT_NOP;
264         }
265         else {
266           *fmt = FMT_TYPE_COMMENT;
267         }
268         /* Handle other comments */
269       }
270       else {
271         find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
272         if (*str == find) {
273           cont = 0;
274         }
275         *fmt = FMT_TYPE_STRING;
276       }
277 
278       str += BLI_str_utf8_size_safe(str) - 1;
279     }
280     /* Not in a string... */
281     else {
282       /* Deal with comments first */
283       if (*str == '/' && *(str + 1) == '/') {
284         /* fill the remaining line */
285         text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - (int)(fmt - line->format));
286       }
287       /* C-Style (multi-line) comments */
288       else if (*str == '/' && *(str + 1) == '*') {
289         cont = FMT_CONT_COMMENT_C;
290         *fmt = FMT_TYPE_COMMENT;
291         fmt++;
292         str++;
293         *fmt = FMT_TYPE_COMMENT;
294       }
295       else if (*str == '"' || *str == '\'') {
296         /* Strings */
297         find = *str;
298         cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
299         *fmt = FMT_TYPE_STRING;
300       }
301       /* Whitespace (all ws. has been converted to spaces) */
302       else if (*str == ' ') {
303         *fmt = FMT_TYPE_WHITESPACE;
304       }
305       /* Numbers (digits not part of an identifier and periods followed by digits) */
306       else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) ||
307                (*str == '.' && text_check_digit(*(str + 1)))) {
308         *fmt = FMT_TYPE_NUMERAL;
309       }
310       /* Punctuation */
311       else if ((*str != '#') && text_check_delim(*str)) {
312         *fmt = FMT_TYPE_SYMBOL;
313       }
314       /* Identifiers and other text (no previous ws. or delims. so text continues) */
315       else if (prev == FMT_TYPE_DEFAULT) {
316         str += BLI_str_utf8_size_safe(str) - 1;
317         *fmt = FMT_TYPE_DEFAULT;
318       }
319       /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
320       else {
321         /* Keep aligned args for readability. */
322         /* clang-format off */
323 
324         /* Special vars(v) or built-in keywords(b) */
325         /* keep in sync with 'txtfmt_osl_format_identifier()' */
326         if        ((i = txtfmt_osl_find_specialvar(str))   != -1) { prev = FMT_TYPE_SPECIAL;
327         } else if ((i = txtfmt_osl_find_builtinfunc(str))  != -1) { prev = FMT_TYPE_KEYWORD;
328         } else if ((i = txtfmt_osl_find_reserved(str))     != -1) { prev = FMT_TYPE_RESERVED;
329         } else if ((i = txtfmt_osl_find_preprocessor(str)) != -1) { prev = FMT_TYPE_DIRECTIVE;
330         }
331 
332         /* clang-format on */
333 
334         if (i > 0) {
335           if (prev == FMT_TYPE_DIRECTIVE) { /* can contain utf8 */
336             text_format_fill(&str, &fmt, prev, i);
337           }
338           else {
339             text_format_fill_ascii(&str, &fmt, prev, i);
340           }
341         }
342         else {
343           str += BLI_str_utf8_size_safe(str) - 1;
344           *fmt = FMT_TYPE_DEFAULT;
345         }
346       }
347     }
348     prev = *fmt;
349     fmt++;
350     str++;
351   }
352 
353   /* Terminate and add continuation char */
354   *fmt = '\0';
355   fmt++;
356   *fmt = cont;
357 
358   /* If continuation has changed and we're allowed, process the next line */
359   if (cont != cont_orig && do_next && line->next) {
360     txtfmt_osl_format_line(st, line->next, do_next);
361   }
362 
363   flatten_string_free(&fs);
364 }
365 
ED_text_format_register_osl(void)366 void ED_text_format_register_osl(void)
367 {
368   static TextFormatType tft = {NULL};
369   static const char *ext[] = {"osl", NULL};
370 
371   tft.format_identifier = txtfmt_osl_format_identifier;
372   tft.format_line = txtfmt_osl_format_line;
373   tft.ext = ext;
374 
375   ED_text_format_register(&tft);
376 }
377