rapydscript-ng 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bin/build.ts +29 -0
  3. package/local-agent.md +4 -0
  4. package/package.json +1 -1
  5. package/release/baselib-plain-pretty.js +85 -61
  6. package/release/baselib-plain-ugly.js +3 -3
  7. package/release/compiler.js +9661 -9540
  8. package/release/signatures.json +26 -26
  9. package/src/ast.pyj +169 -64
  10. package/src/baselib-builtins.pyj +35 -14
  11. package/src/baselib-containers.pyj +70 -40
  12. package/src/baselib-internal.pyj +20 -8
  13. package/src/baselib-itertools.pyj +6 -2
  14. package/src/baselib-str.pyj +74 -28
  15. package/src/errors.pyj +4 -1
  16. package/src/lib/aes.pyj +664 -35
  17. package/src/lib/elementmaker.pyj +105 -13
  18. package/src/lib/encodings.pyj +31 -7
  19. package/src/lib/gettext.pyj +6 -3
  20. package/src/lib/math.pyj +38 -22
  21. package/src/lib/pythonize.pyj +5 -3
  22. package/src/lib/random.pyj +13 -5
  23. package/src/lib/re.pyj +81 -18
  24. package/src/lib/traceback.pyj +42 -10
  25. package/src/lib/uuid.pyj +2 -1
  26. package/src/output/classes.pyj +70 -26
  27. package/src/output/codegen.pyj +92 -20
  28. package/src/output/comments.pyj +11 -4
  29. package/src/output/exceptions.pyj +17 -4
  30. package/src/output/functions.pyj +145 -26
  31. package/src/output/literals.pyj +7 -2
  32. package/src/output/loops.pyj +79 -23
  33. package/src/output/modules.pyj +41 -12
  34. package/src/output/operators.pyj +154 -31
  35. package/src/output/statements.pyj +38 -10
  36. package/src/output/stream.pyj +43 -12
  37. package/src/output/utils.pyj +10 -3
  38. package/src/parse.pyj +740 -259
  39. package/src/string_interpolation.pyj +4 -1
  40. package/src/tokenizer.pyj +314 -57
  41. package/src/unicode_aliases.pyj +4 -2
  42. package/src/utils.pyj +19 -5
  43. package/test/functions.pyj +8 -0
  44. package/test/generic.pyj +9 -0
  45. package/test/lsp.pyj +16 -0
  46. package/test/str.pyj +10 -0
  47. package/tools/cli.mjs +4 -3
  48. package/tools/compile.mjs +9 -3
  49. package/tools/compiler.mjs +0 -6
  50. package/tools/fmt.mjs +41 -2
  51. package/tools/lint-worker.mjs +107 -0
  52. package/tools/lint.mjs +134 -8
  53. package/tools/lsp.mjs +11 -1
  54. package/tree-sitter/grammar.js +27 -6
  55. package/tree-sitter/queries/highlights.scm +10 -0
  56. package/tree-sitter/src/grammar.json +130 -5
  57. package/tree-sitter/src/node-types.json +73 -0
  58. package/tree-sitter/src/parser.c +119589 -114993
  59. package/tree-sitter/src/scanner.c +275 -17
  60. package/tree-sitter/test/corpus/strings.txt +41 -5
@@ -9,8 +9,17 @@ enum TokenType {
9
9
  INDENT,
10
10
  DEDENT,
11
11
  REGEX,
12
+ FSTRING_START,
13
+ FSTRING_CONTENT,
14
+ FSTRING_END,
12
15
  };
13
16
 
17
+ // Per-nesting-level metadata for f-strings.
18
+ typedef struct {
19
+ char quote_char; // '"' or '\''
20
+ uint8_t triple; // 1 if triple-quoted, 0 otherwise
21
+ } FStringFrame;
22
+
14
23
  // A simple growable stack of indentation widths. The bottom of the stack is
15
24
  // always 0 (the module level indentation).
16
25
  typedef struct {
@@ -24,6 +33,10 @@ typedef struct {
24
33
  // (already consumed) newline.
25
34
  uint16_t pending_indent;
26
35
  uint8_t has_pending;
36
+ // Stack of open f-string frames.
37
+ uint32_t fstring_size;
38
+ uint32_t fstring_capacity;
39
+ FStringFrame *fstring_data;
27
40
  } Scanner;
28
41
 
29
42
  static void stack_push(Scanner *s, uint16_t value) {
@@ -46,6 +59,21 @@ static void stack_pop(Scanner *s) {
46
59
  }
47
60
  }
48
61
 
62
+ static void fstack_push(Scanner *s, FStringFrame frame) {
63
+ if (s->fstring_size >= s->fstring_capacity) {
64
+ uint32_t new_cap = s->fstring_capacity ? s->fstring_capacity * 2 : 4;
65
+ s->fstring_data = realloc(s->fstring_data, new_cap * sizeof(FStringFrame));
66
+ s->fstring_capacity = new_cap;
67
+ }
68
+ s->fstring_data[s->fstring_size++] = frame;
69
+ }
70
+
71
+ static void fstack_pop(Scanner *s) {
72
+ if (s->fstring_size > 0) {
73
+ s->fstring_size--;
74
+ }
75
+ }
76
+
49
77
  void *tree_sitter_rapydscript_external_scanner_create(void) {
50
78
  Scanner *s = calloc(1, sizeof(Scanner));
51
79
  stack_push(s, 0);
@@ -57,23 +85,43 @@ void tree_sitter_rapydscript_external_scanner_destroy(void *payload) {
57
85
  if (s->data) {
58
86
  free(s->data);
59
87
  }
88
+ if (s->fstring_data) {
89
+ free(s->fstring_data);
90
+ }
60
91
  free(s);
61
92
  }
62
93
 
63
94
  unsigned tree_sitter_rapydscript_external_scanner_serialize(void *payload, char *buffer) {
64
95
  Scanner *s = (Scanner *)payload;
65
96
  unsigned i = 0;
97
+
98
+ // has_pending flag and pending_indent value
66
99
  buffer[i++] = (char)(s->has_pending ? 1 : 0);
67
100
  buffer[i++] = (char)(s->pending_indent & 0xFF);
68
101
  buffer[i++] = (char)((s->pending_indent >> 8) & 0xFF);
102
+
103
+ // Indent stack size (so we can distinguish it from fstring data)
104
+ uint16_t indent_count = (uint16_t)(s->size < 0xFFFF ? s->size : 0xFFFF);
105
+ buffer[i++] = (char)(indent_count & 0xFF);
106
+ buffer[i++] = (char)((indent_count >> 8) & 0xFF);
107
+
69
108
  for (uint32_t k = 0; k < s->size; k++) {
70
- if (i + 2 > TREE_SITTER_SERIALIZATION_BUFFER_SIZE) {
71
- break;
72
- }
109
+ if (i + 2 > TREE_SITTER_SERIALIZATION_BUFFER_SIZE) break;
73
110
  uint16_t v = s->data[k];
74
111
  buffer[i++] = (char)(v & 0xFF);
75
112
  buffer[i++] = (char)((v >> 8) & 0xFF);
76
113
  }
114
+
115
+ // F-string stack
116
+ if (i + 1 <= TREE_SITTER_SERIALIZATION_BUFFER_SIZE) {
117
+ buffer[i++] = (char)(s->fstring_size & 0xFF);
118
+ for (uint32_t k = 0; k < s->fstring_size; k++) {
119
+ if (i + 2 > TREE_SITTER_SERIALIZATION_BUFFER_SIZE) break;
120
+ buffer[i++] = s->fstring_data[k].quote_char;
121
+ buffer[i++] = s->fstring_data[k].triple;
122
+ }
123
+ }
124
+
77
125
  return i;
78
126
  }
79
127
 
@@ -82,13 +130,22 @@ void tree_sitter_rapydscript_external_scanner_deserialize(void *payload, const c
82
130
  s->size = 0;
83
131
  s->has_pending = 0;
84
132
  s->pending_indent = 0;
133
+ s->fstring_size = 0;
85
134
  unsigned i = 0;
86
- if (length >= 3) {
87
- s->has_pending = (uint8_t)buffer[i++];
88
- s->pending_indent = (uint8_t)buffer[i] | ((uint16_t)(uint8_t)buffer[i + 1] << 8);
89
- i += 2;
135
+
136
+ if (length < 5) {
137
+ stack_push(s, 0);
138
+ return;
90
139
  }
91
- while (i + 2 <= length) {
140
+
141
+ s->has_pending = (uint8_t)buffer[i++];
142
+ s->pending_indent = (uint8_t)buffer[i] | ((uint16_t)(uint8_t)buffer[i + 1] << 8);
143
+ i += 2;
144
+
145
+ uint16_t indent_count = (uint8_t)buffer[i] | ((uint16_t)(uint8_t)buffer[i + 1] << 8);
146
+ i += 2;
147
+
148
+ for (uint16_t k = 0; k < indent_count && i + 2 <= length; k++) {
92
149
  uint16_t v = (uint8_t)buffer[i] | ((uint16_t)(uint8_t)buffer[i + 1] << 8);
93
150
  i += 2;
94
151
  stack_push(s, v);
@@ -96,6 +153,17 @@ void tree_sitter_rapydscript_external_scanner_deserialize(void *payload, const c
96
153
  if (s->size == 0) {
97
154
  stack_push(s, 0);
98
155
  }
156
+
157
+ // F-string stack
158
+ if (i < length) {
159
+ uint8_t fcount = (uint8_t)buffer[i++];
160
+ for (uint8_t k = 0; k < fcount && i + 2 <= length; k++) {
161
+ FStringFrame frame;
162
+ frame.quote_char = buffer[i++];
163
+ frame.triple = (uint8_t)buffer[i++];
164
+ fstack_push(s, frame);
165
+ }
166
+ }
99
167
  }
100
168
 
101
169
  static void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
@@ -178,10 +246,196 @@ static bool scan_regex(TSLexer *lexer) {
178
246
  return true;
179
247
  }
180
248
 
249
+ // Scan the opening of an f-string: optional modifier chars containing at least
250
+ // one f/F (but no v/V), followed by 1 or 3 quote characters.
251
+ static bool scan_fstring_start(TSLexer *lexer, Scanner *s) {
252
+ // Skip leading whitespace (the extras rule handles it for most tokens, but
253
+ // we do it here explicitly to match the REGEX scanner's behaviour).
254
+ while (lexer->lookahead == ' ' || lexer->lookahead == '\t' ||
255
+ lexer->lookahead == '\n' || lexer->lookahead == '\r' ||
256
+ lexer->lookahead == '\f') {
257
+ skip(lexer);
258
+ }
259
+
260
+ bool has_f = false;
261
+ int mod_count = 0;
262
+
263
+ while (mod_count < 6) {
264
+ int32_t c = lexer->lookahead;
265
+ if (c == 'f' || c == 'F') {
266
+ has_f = true;
267
+ advance(lexer);
268
+ mod_count++;
269
+ } else if (c == 'r' || c == 'R' || c == 'b' || c == 'B' ||
270
+ c == 'u' || c == 'U') {
271
+ advance(lexer);
272
+ mod_count++;
273
+ } else if (c == 'v' || c == 'V') {
274
+ // Verbatim string — not an f-string.
275
+ return false;
276
+ } else {
277
+ break;
278
+ }
279
+ }
280
+
281
+ if (!has_f) return false;
282
+
283
+ int32_t q = lexer->lookahead;
284
+ if (q != '"' && q != '\'') return false;
285
+
286
+ advance(lexer); // consume first quote
287
+ lexer->mark_end(lexer); // tentative token end after one quote
288
+
289
+ bool triple = false;
290
+ if (lexer->lookahead == q) {
291
+ advance(lexer); // peek at second quote
292
+ if (lexer->lookahead == q) {
293
+ advance(lexer); // consume third quote
294
+ triple = true;
295
+ lexer->mark_end(lexer); // extend token to include triple quote
296
+ }
297
+ // If only two quotes (f''), mark_end is still after the first quote;
298
+ // the second quote will be scanned as FSTRING_END immediately.
299
+ }
300
+
301
+ FStringFrame frame = {(char)q, triple ? 1 : 0};
302
+ fstack_push(s, frame);
303
+
304
+ lexer->result_symbol = FSTRING_START;
305
+ return true;
306
+ }
307
+
308
+ // Scan literal content inside an f-string up to (but not including) the next
309
+ // interpolation opener '{' or the closing quote sequence. Returns false if
310
+ // there is no content (e.g. we are already at '{' or the closing quote).
311
+ static bool scan_fstring_content(TSLexer *lexer, Scanner *s) {
312
+ if (s->fstring_size == 0) return false;
313
+ FStringFrame frame = s->fstring_data[s->fstring_size - 1];
314
+ int32_t q = (int32_t)(unsigned char)frame.quote_char;
315
+ bool triple = frame.triple != 0;
316
+
317
+ bool has_content = false;
318
+
319
+ while (true) {
320
+ int32_t c = lexer->lookahead;
321
+
322
+ if (c == 0) break; // EOF
323
+
324
+ // Unterminated single-line f-string.
325
+ if (c == '\n' && !triple) break;
326
+
327
+ if (c == q) {
328
+ if (triple) {
329
+ // Check for closing triple quote.
330
+ advance(lexer);
331
+ if (lexer->lookahead == q) {
332
+ advance(lexer);
333
+ if (lexer->lookahead == q) {
334
+ // Found closing """/'''. Stop before it.
335
+ // mark_end was last set before we advanced into the quotes.
336
+ break;
337
+ }
338
+ // Two matching quotes but not three: include both in content.
339
+ lexer->mark_end(lexer);
340
+ has_content = true;
341
+ continue;
342
+ }
343
+ // Single matching quote in a triple-quoted string: part of content.
344
+ lexer->mark_end(lexer);
345
+ has_content = true;
346
+ continue;
347
+ } else {
348
+ // Single-quoted: closing quote. Stop before it.
349
+ break;
350
+ }
351
+ }
352
+
353
+ if (c == '{') {
354
+ advance(lexer); // tentatively consume
355
+ if (lexer->lookahead == '{') {
356
+ // '{{' is an escaped brace — include both in content.
357
+ advance(lexer);
358
+ lexer->mark_end(lexer);
359
+ has_content = true;
360
+ continue;
361
+ }
362
+ // Lone '{' starts an interpolation. The last mark_end is before
363
+ // this '{', so returning false here leaves the '{' unconsumed.
364
+ break;
365
+ }
366
+
367
+ if (c == '}') {
368
+ advance(lexer); // tentatively consume
369
+ if (lexer->lookahead == '}') {
370
+ // '}}' is an escaped brace — include both in content.
371
+ advance(lexer);
372
+ lexer->mark_end(lexer);
373
+ has_content = true;
374
+ continue;
375
+ }
376
+ // Lone '}' — either a format-spec terminator or a syntax error.
377
+ // Stop before it.
378
+ break;
379
+ }
380
+
381
+ if (c == '\\') {
382
+ advance(lexer); // consume backslash
383
+ if (lexer->lookahead != 0) {
384
+ advance(lexer); // consume escaped character
385
+ }
386
+ lexer->mark_end(lexer);
387
+ has_content = true;
388
+ continue;
389
+ }
390
+
391
+ advance(lexer);
392
+ lexer->mark_end(lexer);
393
+ has_content = true;
394
+ }
395
+
396
+ if (!has_content) return false;
397
+
398
+ lexer->result_symbol = FSTRING_CONTENT;
399
+ return true;
400
+ }
401
+
402
+ // Scan the closing quote sequence of an f-string and pop the frame.
403
+ static bool scan_fstring_end(TSLexer *lexer, Scanner *s) {
404
+ if (s->fstring_size == 0) return false;
405
+ FStringFrame frame = s->fstring_data[s->fstring_size - 1];
406
+ int32_t q = (int32_t)(unsigned char)frame.quote_char;
407
+
408
+ if (lexer->lookahead != q) return false;
409
+
410
+ if (frame.triple) {
411
+ advance(lexer);
412
+ if (lexer->lookahead != q) return false;
413
+ advance(lexer);
414
+ if (lexer->lookahead != q) return false;
415
+ advance(lexer);
416
+ } else {
417
+ advance(lexer);
418
+ }
419
+
420
+ fstack_pop(s);
421
+ lexer->mark_end(lexer);
422
+ lexer->result_symbol = FSTRING_END;
423
+ return true;
424
+ }
425
+
181
426
  bool tree_sitter_rapydscript_external_scanner_scan(void *payload, TSLexer *lexer,
182
427
  const bool *valid_symbols) {
183
428
  Scanner *scanner = (Scanner *)payload;
184
429
 
430
+ // F-string content must be handled before any whitespace skipping because
431
+ // the whitespace is part of the string's content.
432
+ if (valid_symbols[FSTRING_CONTENT]) {
433
+ if (scan_fstring_content(lexer, scanner)) return true;
434
+ }
435
+ if (valid_symbols[FSTRING_END]) {
436
+ if (scan_fstring_end(lexer, scanner)) return true;
437
+ }
438
+
185
439
  // 1. Reconcile any pending indentation left over from a previously emitted
186
440
  // NEWLINE/DEDENT. This is how we emit several DEDENT tokens for a single
187
441
  // line without re-reading the (already consumed) newline. Only DEDENT is
@@ -200,25 +454,29 @@ bool tree_sitter_rapydscript_external_scanner_scan(void *payload, TSLexer *lexer
200
454
  scanner->has_pending = 0;
201
455
  }
202
456
 
203
- // 2. Regular expression literals. Only valid where a primary expression may
204
- // begin, so '/' cannot be a division operator in that position.
205
- if (valid_symbols[REGEX] && !valid_symbols[NEWLINE] && !valid_symbols[INDENT] &&
206
- !valid_symbols[DEDENT]) {
207
- // Skip surrounding whitespace, including newlines: a regex literal may
208
- // appear on its own line, e.g. as an argument following a preceding
209
- // multi-line one (`foo(\n def(): ...\n ,\n /re/\n)`).
457
+ // 2. Regular expression literals and f-string starts are both only valid
458
+ // where a primary expression may begin, so neither '/' (division) nor an
459
+ // f-modifier identifier causes ambiguity in other positions.
460
+ if ((valid_symbols[REGEX] || valid_symbols[FSTRING_START]) &&
461
+ !valid_symbols[NEWLINE] && !valid_symbols[INDENT] && !valid_symbols[DEDENT]) {
462
+ // Skip surrounding whitespace, including newlines: a literal may appear
463
+ // on its own line, e.g. as an argument following a preceding multi-line
464
+ // one (`foo(\n def(): ...\n ,\n /re/\n)`).
210
465
  while (lexer->lookahead == ' ' || lexer->lookahead == '\t' ||
211
466
  lexer->lookahead == '\n' || lexer->lookahead == '\r' ||
212
467
  lexer->lookahead == '\f') {
213
468
  skip(lexer);
214
469
  }
215
- if (lexer->lookahead == '/') {
470
+ if (valid_symbols[REGEX] && lexer->lookahead == '/') {
216
471
  return scan_regex(lexer);
217
472
  }
473
+ if (valid_symbols[FSTRING_START]) {
474
+ return scan_fstring_start(lexer, scanner);
475
+ }
218
476
  return false;
219
477
  }
220
478
 
221
- // 3. Indentation / newline handling.
479
+ // 4. Indentation / newline handling.
222
480
  bool found_end_of_line = false;
223
481
  uint32_t indent_length = 0;
224
482
 
@@ -33,13 +33,11 @@ line"""
33
33
  right: (string)))
34
34
 
35
35
  ==================
36
- string modifiers r u f
36
+ string modifiers r u
37
37
  ==================
38
38
 
39
39
  a = r'x'
40
40
  b = u'y'
41
- c = f'val {x}'
42
-
43
41
 
44
42
  ---
45
43
 
@@ -51,11 +49,49 @@ c = f'val {x}'
51
49
  (assignment
52
50
  left: (pattern
53
51
  (identifier))
54
- right: (string))
52
+ right: (string)))
53
+
54
+ ==================
55
+ f-string basic interpolation
56
+ ==================
57
+
58
+ c = f'val {x}'
59
+
60
+ ---
61
+
62
+ (module
55
63
  (assignment
56
64
  left: (pattern
57
65
  (identifier))
58
- right: (string)))
66
+ right: (f_string
67
+ (fstring_start)
68
+ (fstring_content)
69
+ (interpolation
70
+ expression: (identifier))
71
+ (fstring_end))))
72
+
73
+ ==================
74
+ f-string expression
75
+ ==================
76
+
77
+ d = f'{obj.method(arg)}'
78
+
79
+ ---
80
+
81
+ (module
82
+ (assignment
83
+ left: (pattern
84
+ (identifier))
85
+ right: (f_string
86
+ (fstring_start)
87
+ (interpolation
88
+ expression: (call
89
+ function: (attribute
90
+ object: (identifier)
91
+ attribute: (identifier))
92
+ arguments: (argument_list
93
+ (identifier))))
94
+ (fstring_end))))
59
95
 
60
96
  ==================
61
97
  verbatim javascript literal