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