Blender  V2.93
text_format_py.c
Go to the documentation of this file.
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 
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 
44 static int txtfmt_py_find_builtinfunc(const char *string)
45 {
46  int i, len;
47  /* list is from...
48  * ", ".join(['"%s"' % kw
49  * for kw in __import__("keyword").kwlist
50  * if kw not in {"False", "None", "True", "def", "class"}])
51  *
52  * ... and for this code:
53  * print("\n".join(['else if (STR_LITERAL_STARTSWITH(string, "%s", len)) i = len;' % kw
54  * for kw in __import__("keyword").kwlist
55  * if kw not in {"False", "None", "True", "def", "class"}]))
56  */
57 
58  /* Keep aligned args for readability. */
59  /* clang-format off */
60 
61  if (STR_LITERAL_STARTSWITH(string, "assert", len)) { i = len;
62  } else if (STR_LITERAL_STARTSWITH(string, "async", len)) { i = len;
63  } else if (STR_LITERAL_STARTSWITH(string, "await", len)) { i = len;
64  } else if (STR_LITERAL_STARTSWITH(string, "and", len)) { i = len;
65  } else if (STR_LITERAL_STARTSWITH(string, "as", len)) { i = len;
66  } else if (STR_LITERAL_STARTSWITH(string, "break", len)) { i = len;
67  } else if (STR_LITERAL_STARTSWITH(string, "continue", len)) { i = len;
68  } else if (STR_LITERAL_STARTSWITH(string, "del", len)) { i = len;
69  } else if (STR_LITERAL_STARTSWITH(string, "elif", len)) { i = len;
70  } else if (STR_LITERAL_STARTSWITH(string, "else", len)) { i = len;
71  } else if (STR_LITERAL_STARTSWITH(string, "except", len)) { i = len;
72  } else if (STR_LITERAL_STARTSWITH(string, "finally", len)) { i = len;
73  } else if (STR_LITERAL_STARTSWITH(string, "for", len)) { i = len;
74  } else if (STR_LITERAL_STARTSWITH(string, "from", len)) { i = len;
75  } else if (STR_LITERAL_STARTSWITH(string, "global", len)) { i = len;
76  } else if (STR_LITERAL_STARTSWITH(string, "if", len)) { i = len;
77  } else if (STR_LITERAL_STARTSWITH(string, "import", len)) { i = len;
78  } else if (STR_LITERAL_STARTSWITH(string, "in", len)) { i = len;
79  } else if (STR_LITERAL_STARTSWITH(string, "is", len)) { i = len;
80  } else if (STR_LITERAL_STARTSWITH(string, "lambda", len)) { i = len;
81  } else if (STR_LITERAL_STARTSWITH(string, "nonlocal", len)) { i = len;
82  } else if (STR_LITERAL_STARTSWITH(string, "not", len)) { i = len;
83  } else if (STR_LITERAL_STARTSWITH(string, "or", len)) { i = len;
84  } else if (STR_LITERAL_STARTSWITH(string, "pass", len)) { i = len;
85  } else if (STR_LITERAL_STARTSWITH(string, "raise", len)) { i = len;
86  } else if (STR_LITERAL_STARTSWITH(string, "return", len)) { i = len;
87  } else if (STR_LITERAL_STARTSWITH(string, "try", len)) { i = len;
88  } else if (STR_LITERAL_STARTSWITH(string, "while", len)) { i = len;
89  } else if (STR_LITERAL_STARTSWITH(string, "with", len)) { i = len;
90  } else if (STR_LITERAL_STARTSWITH(string, "yield", len)) { i = len;
91  } else { i = 0;
92  }
93 
94  /* clang-format on */
95 
96  /* If next source char is an identifier (eg. 'i' in "definite") no match */
97  if (i == 0 || text_check_identifier(string[i])) {
98  return -1;
99  }
100  return i;
101 }
102 
103 /* Checks the specified source string for a Python special name. This name must
104  * start at the beginning of the source string and must be followed by a non-
105  * identifier (see text_check_identifier(char)) or null character.
106  *
107  * If a special name is found, the length of the matching name is returned.
108  * Otherwise, -1 is returned. */
109 
110 static int txtfmt_py_find_specialvar(const char *string)
111 {
112  int i, len;
113 
114  /* Keep aligned args for readability. */
115  /* clang-format off */
116 
117  if (STR_LITERAL_STARTSWITH(string, "def", len)) { i = len;
118  } else if (STR_LITERAL_STARTSWITH(string, "class", len)) { i = len;
119  } else { i = 0;
120  }
121 
122  /* clang-format on */
123 
124  /* If next source char is an identifier (eg. 'i' in "definite") no match */
125  if (i == 0 || text_check_identifier(string[i])) {
126  return -1;
127  }
128  return i;
129 }
130 
131 static int txtfmt_py_find_decorator(const char *string)
132 {
133  if (string[0] != '@') {
134  return -1;
135  }
136  if (!text_check_identifier(string[1])) {
137  return -1;
138  }
139  /* Interpret as matrix multiplication when followed by whitespace. */
140  if (text_check_whitespace(string[1])) {
141  return -1;
142  }
143 
144  int i = 1;
145  while (text_check_identifier(string[i])) {
146  i++;
147  }
148  return i;
149 }
150 
151 static int txtfmt_py_find_bool(const char *string)
152 {
153  int i, len;
154 
155  /* Keep aligned args for readability. */
156  /* clang-format off */
157 
158  if (STR_LITERAL_STARTSWITH(string, "None", len)) { i = len;
159  } else if (STR_LITERAL_STARTSWITH(string, "True", len)) { i = len;
160  } else if (STR_LITERAL_STARTSWITH(string, "False", len)) { i = len;
161  } else { i = 0;
162  }
163 
164  /* clang-format on */
165 
166  /* If next source char is an identifier (eg. 'i' in "Nonetheless") no match */
167  if (i == 0 || text_check_identifier(string[i])) {
168  return -1;
169  }
170  return i;
171 }
172 
173 /* Numeral character matching. */
174 #define TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL(txtfmt_py_numeral_char_is_fn) \
175  { \
176  uint count = 0; \
177  for (; txtfmt_py_numeral_char_is_fn(*string); string += 1) { \
178  count += 1; \
179  } \
180  return count; \
181  } \
182  ((void)0)
183 
184 /* Binary. */
185 static bool txtfmt_py_numeral_char_is_binary(const char c)
186 {
187  return ELEM(c, '0', '1') || (c == '_');
188 }
189 static uint txtfmt_py_numeral_string_count_binary(const char *string)
190 {
192 }
193 
194 /* Octal. */
195 static bool txtfmt_py_numeral_char_is_octal(const char c)
196 {
197  return (c >= '0' && c <= '7') || (c == '_');
198 }
199 static uint txtfmt_py_numeral_string_count_octal(const char *string)
200 {
202 }
203 
204 /* Decimal. */
205 static bool txtfmt_py_numeral_char_is_decimal(const char c)
206 {
207  return (c >= '0' && c <= '9') || (c == '_');
208 }
210 {
212 }
213 
214 /* Hexadecimal. */
216 {
217  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c == '_');
218 }
220 {
222 }
223 
224 /* Zeros. */
225 static bool txtfmt_py_numeral_char_is_zero(const char c)
226 {
227  return (ELEM(c, '0', '_'));
228 }
229 static uint txtfmt_py_numeral_string_count_zeros(const char *string)
230 {
232 }
233 
234 #undef TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL
235 
236 static int txtfmt_py_find_numeral_inner(const char *string)
237 {
238  if (string == NULL || *string == '\0') {
239  return -1;
240  }
241 
242  const char first = *string, second = *(string + 1);
243 
244  /* Decimal dot must be followed by a digit, any decimal digit.
245  * Note that the there can be any number of leading zeros after
246  * the decimal point (leading zeros are not allowed in integers) */
247  if (first == '.') {
248  if (text_check_digit(second)) {
249  return 1 + txtfmt_py_numeral_string_count_decimal(string + 1);
250  }
251  }
252  else if (first == '0') {
253  /* Numerals starting with '0x' or '0X' is followed by hexadecimal digits. */
254  if (ELEM(second, 'x', 'X')) {
255  return 2 + txtfmt_py_numeral_string_count_hexadecimal(string + 2);
256  }
257  /* Numerals starting with '0o' or '0O' is followed by octal digits. */
258  if (ELEM(second, 'o', 'O')) {
259  return 2 + txtfmt_py_numeral_string_count_octal(string + 2);
260  }
261  /* Numerals starting with '0b' or '0B' is followed by binary digits. */
262  if (ELEM(second, 'b', 'B')) {
263  return 2 + txtfmt_py_numeral_string_count_binary(string + 2);
264  }
265  /* Other numerals starting with '0' can be followed by any number of '0' characters. */
266  if (ELEM(second, '0', '_')) {
267  return 2 + txtfmt_py_numeral_string_count_zeros(string + 2);
268  }
269  }
270  /* Any non-zero digit is the start of a decimal number. */
271  else if (first > '0' && first <= '9') {
272  return 1 + txtfmt_py_numeral_string_count_decimal(string + 1);
273  }
274  /* A single zero is also allowed. */
275  return (first == '0') ? 1 : 0;
276 }
277 
278 static int txtfmt_py_literal_numeral(const char *string, char prev_fmt)
279 {
280  if (string == NULL || *string == '\0') {
281  return -1;
282  }
283 
284  const char first = *string, second = *(string + 1);
285 
286  if (prev_fmt == FMT_TYPE_NUMERAL) {
287  /* Previous was a number; if immediately followed by 'e' or 'E' and a digit,
288  * it's a base 10 exponent (scientific notation). */
289  if (ELEM(first, 'e', 'E') && (text_check_digit(second) || second == '-')) {
290  return 1 + txtfmt_py_find_numeral_inner(string + 1);
291  }
292  /* Previous was a number; if immediately followed by '.' it's a floating point decimal number.
293  * Note: keep the decimal point, it's needed to allow leading zeros. */
294  if (first == '.') {
295  return txtfmt_py_find_numeral_inner(string);
296  }
297  /* "Imaginary" part of a complex number ends with 'j' */
298  if (ELEM(first, 'j', 'J') && !text_check_digit(second)) {
299  return 1;
300  }
301  }
302  else if ((prev_fmt != FMT_TYPE_DEFAULT) &&
303  (text_check_digit(first) || (first == '.' && text_check_digit(second)))) {
304  /* New numeral, starting with a digit or a decimal point followed by a digit. */
305  return txtfmt_py_find_numeral_inner(string);
306  }
307  /* Not a literal numeral. */
308  return 0;
309 }
310 
311 static char txtfmt_py_format_identifier(const char *str)
312 {
313  char fmt;
314 
315  /* Keep aligned args for readability. */
316  /* clang-format off */
317 
318  if ((txtfmt_py_find_specialvar(str)) != -1) { fmt = FMT_TYPE_SPECIAL;
319  } else if ((txtfmt_py_find_builtinfunc(str)) != -1) { fmt = FMT_TYPE_KEYWORD;
320  } else if ((txtfmt_py_find_decorator(str)) != -1) { fmt = FMT_TYPE_RESERVED;
321  } else { fmt = FMT_TYPE_DEFAULT;
322  }
323 
324  /* clang-format on */
325  return fmt;
326 }
327 
328 static void txtfmt_py_format_line(SpaceText *st, TextLine *line, const bool do_next)
329 {
330  FlattenString fs;
331  const char *str;
332  char *fmt;
333  char cont_orig, cont, find, prev = ' ';
334  int len, i;
335 
336  /* Get continuation from previous line */
337  if (line->prev && line->prev->format != NULL) {
338  fmt = line->prev->format;
339  cont = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
340  BLI_assert((FMT_CONT_ALL & cont) == cont);
341  }
342  else {
343  cont = FMT_CONT_NOP;
344  }
345 
346  /* Get original continuation from this line */
347  if (line->format != NULL) {
348  fmt = line->format;
349  cont_orig = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
350  BLI_assert((FMT_CONT_ALL & cont_orig) == cont_orig);
351  }
352  else {
353  cont_orig = 0xFF;
354  }
355 
356  len = flatten_string(st, &fs, line->line);
357  str = fs.buf;
358  if (!text_check_format_len(line, len)) {
359  flatten_string_free(&fs);
360  return;
361  }
362  fmt = line->format;
363 
364  while (*str) {
365  /* Handle escape sequences by skipping both \ and next char */
366  if (*str == '\\') {
367  *fmt = prev;
368  fmt++;
369  str++;
370  if (*str == '\0') {
371  break;
372  }
373  *fmt = prev;
374  fmt++;
376  continue;
377  }
378  /* Handle continuations */
379  if (cont) {
380  /* Triple strings ("""...""" or '''...''') */
381  if (cont & FMT_CONT_TRIPLE) {
382  find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
383  if (*str == find && *(str + 1) == find && *(str + 2) == find) {
384  *fmt = FMT_TYPE_STRING;
385  fmt++;
386  str++;
387  *fmt = FMT_TYPE_STRING;
388  fmt++;
389  str++;
390  cont = FMT_CONT_NOP;
391  }
392  /* Handle other strings */
393  }
394  else {
395  find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
396  if (*str == find) {
397  cont = FMT_CONT_NOP;
398  }
399  }
400 
401  *fmt = FMT_TYPE_STRING;
403  }
404  /* Not in a string... */
405  else {
406  /* Deal with comments first */
407  if (*str == '#') {
408  /* fill the remaining line */
409  text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - (int)(fmt - line->format));
410  }
411  else if (ELEM(*str, '"', '\'')) {
412  /* Strings */
413  find = *str;
414  cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
415  if (*(str + 1) == find && *(str + 2) == find) {
416  *fmt = FMT_TYPE_STRING;
417  fmt++;
418  str++;
419  *fmt = FMT_TYPE_STRING;
420  fmt++;
421  str++;
422  cont |= FMT_CONT_TRIPLE;
423  }
424  *fmt = FMT_TYPE_STRING;
425  }
426  /* Whitespace (all ws. has been converted to spaces) */
427  else if (*str == ' ') {
428  *fmt = FMT_TYPE_WHITESPACE;
429  }
430  /* Literal numerals, "numbers". */
431  else if ((i = txtfmt_py_literal_numeral(str, prev)) > 0) {
433  }
434  /* Booleans */
435  else if (prev != FMT_TYPE_DEFAULT && (i = txtfmt_py_find_bool(str)) != -1) {
436  if (i > 0) {
438  }
439  else {
441  *fmt = FMT_TYPE_DEFAULT;
442  }
443  }
444  /* Punctuation */
445  else if ((*str != '@') && text_check_delim(*str)) {
446  *fmt = FMT_TYPE_SYMBOL;
447  }
448  /* Identifiers and other text (no previous ws. or delims. so text continues) */
449  else if (prev == FMT_TYPE_DEFAULT) {
451  *fmt = FMT_TYPE_DEFAULT;
452  }
453  /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
454  else {
455  /* Keep aligned args for readability. */
456  /* clang-format off */
457 
458  /* Special vars(v) or built-in keywords(b) */
459  /* keep in sync with 'txtfmt_py_format_identifier()' */
460  if ((i = txtfmt_py_find_specialvar(str)) != -1) { prev = FMT_TYPE_SPECIAL;
461  } else if ((i = txtfmt_py_find_builtinfunc(str)) != -1) { prev = FMT_TYPE_KEYWORD;
462  } else if ((i = txtfmt_py_find_decorator(str)) != -1) { prev = FMT_TYPE_DIRECTIVE;
463  }
464 
465  /* clang-format on */
466 
467  if (i > 0) {
468  if (prev == FMT_TYPE_DIRECTIVE) { /* can contain utf8 */
469  text_format_fill(&str, &fmt, prev, i);
470  }
471  else {
472  text_format_fill_ascii(&str, &fmt, prev, i);
473  }
474  }
475  else {
477  *fmt = FMT_TYPE_DEFAULT;
478  }
479  }
480  }
481  prev = *fmt;
482  fmt++;
483  str++;
484  }
485 
486  /* Terminate and add continuation char */
487  *fmt = '\0';
488  fmt++;
489  *fmt = cont;
490 
491  /* If continuation has changed and we're allowed, process the next line */
492  if (cont != cont_orig && do_next && line->next) {
493  txtfmt_py_format_line(st, line->next, do_next);
494  }
495 
496  flatten_string_free(&fs);
497 }
498 
500 {
501  static TextFormatType tft = {NULL};
502  static const char *ext[] = {"py", NULL};
503 
506  tft.ext = ext;
507 
509 }
bool text_check_identifier(const char ch)
Definition: text.c:2426
bool text_check_digit(const char ch)
Definition: text.c:2415
bool text_check_delim(const char ch)
Definition: text.c:2402
bool text_check_whitespace(const char ch)
Definition: text.c:2481
#define BLI_assert(a)
Definition: BLI_assert.h:58
int BLI_str_utf8_size_safe(const char *p) ATTR_NONNULL()
Definition: string_utf8.c:508
unsigned int uint
Definition: BLI_sys_types.h:83
#define ELEM(...)
#define str(s)
static unsigned c
Definition: RandGen.cpp:97
void(* format_line)(SpaceText *st, TextLine *line, const bool do_next)
Definition: text_format.h:72
char(* format_identifier)(const char *string)
Definition: text_format.h:61
const char ** ext
Definition: text_format.h:74
char * format
char * line
struct TextLine * prev
struct TextLine * next
int flatten_string(const SpaceText *st, FlattenString *fs, const char *in)
Definition: text_format.c:71
void text_format_fill(const char **str_p, char **fmt_p, const char type, const int len)
Definition: text_format.c:151
void flatten_string_free(FlattenString *fs)
Definition: text_format.c:104
void text_format_fill_ascii(const char **str_p, char **fmt_p, const char type, const int len)
Definition: text_format.c:177
void ED_text_format_register(TextFormatType *tft)
Definition: text_format.c:195
int text_check_format_len(TextLine *line, uint len)
Definition: text_format.c:124
#define STR_LITERAL_STARTSWITH(str, str_literal, len_var)
Definition: text_format.h:110
@ FMT_TYPE_DIRECTIVE
Definition: text_format.h:89
@ FMT_TYPE_STRING
Definition: text_format.h:87
@ FMT_TYPE_COMMENT
Definition: text_format.h:81
@ FMT_TYPE_SPECIAL
Definition: text_format.h:91
@ FMT_TYPE_DEFAULT
Definition: text_format.h:97
@ FMT_TYPE_KEYWORD
Definition: text_format.h:95
@ FMT_TYPE_WHITESPACE
Definition: text_format.h:79
@ FMT_TYPE_NUMERAL
Definition: text_format.h:85
@ FMT_TYPE_RESERVED
Definition: text_format.h:93
@ FMT_TYPE_SYMBOL
Definition: text_format.h:83
@ FMT_CONT_QUOTEDOUBLE
Definition: text_format.h:40
@ FMT_CONT_QUOTESINGLE
Definition: text_format.h:39
@ FMT_CONT_TRIPLE
Definition: text_format.h:41
@ FMT_CONT_NOP
Definition: text_format.h:38
#define FMT_CONT_ALL
Definition: text_format.h:46
static bool txtfmt_py_numeral_char_is_hexadecimal(const char c)
static int txtfmt_py_find_numeral_inner(const char *string)
static uint txtfmt_py_numeral_string_count_hexadecimal(const char *string)
static uint txtfmt_py_numeral_string_count_octal(const char *string)
void ED_text_format_register_py(void)
static bool txtfmt_py_numeral_char_is_decimal(const char c)
#define TXTFMT_PY_NUMERAL_STRING_COUNT_IMPL(txtfmt_py_numeral_char_is_fn)
static char txtfmt_py_format_identifier(const char *str)
static int txtfmt_py_find_specialvar(const char *string)
static int txtfmt_py_find_bool(const char *string)
static int txtfmt_py_find_decorator(const char *string)
static bool txtfmt_py_numeral_char_is_binary(const char c)
static int txtfmt_py_literal_numeral(const char *string, char prev_fmt)
static bool txtfmt_py_numeral_char_is_zero(const char c)
static uint txtfmt_py_numeral_string_count_zeros(const char *string)
static uint txtfmt_py_numeral_string_count_binary(const char *string)
static int txtfmt_py_find_builtinfunc(const char *string)
static void txtfmt_py_format_line(SpaceText *st, TextLine *line, const bool do_next)
static uint txtfmt_py_numeral_string_count_decimal(const char *string)
static bool txtfmt_py_numeral_char_is_octal(const char c)
uint len