xbintsc 0.3.3 → 0.3.5

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 (215) hide show
  1. package/dist/src/ast/expressions.d.ts +8 -0
  2. package/dist/src/binder/binder.js +1 -0
  3. package/dist/src/binder/binder.js.map +1 -1
  4. package/dist/src/binder/references.d.ts +1 -1
  5. package/dist/src/binder/references.js +34 -5
  6. package/dist/src/binder/references.js.map +1 -1
  7. package/dist/src/binder/types.d.ts +4 -0
  8. package/dist/src/binder/types.js.map +1 -1
  9. package/dist/src/cli/main.js.map +1 -1
  10. package/dist/src/codegen/generator/calls/access.d.ts +9 -1
  11. package/dist/src/codegen/generator/calls/access.js +146 -13
  12. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  13. package/dist/src/codegen/generator/calls/closures.d.ts +1 -0
  14. package/dist/src/codegen/generator/calls/closures.js +25 -0
  15. package/dist/src/codegen/generator/calls/closures.js.map +1 -1
  16. package/dist/src/codegen/generator/calls/invocation.js +12 -1
  17. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  18. package/dist/src/codegen/generator/calls/literals.js +12 -4
  19. package/dist/src/codegen/generator/calls/literals.js.map +1 -1
  20. package/dist/src/codegen/generator/calls/super.js +4 -7
  21. package/dist/src/codegen/generator/calls/super.js.map +1 -1
  22. package/dist/src/codegen/generator/context.d.ts +21 -2
  23. package/dist/src/codegen/generator/context.js +42 -5
  24. package/dist/src/codegen/generator/context.js.map +1 -1
  25. package/dist/src/codegen/generator/expressions/assignment.js +1 -3
  26. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  27. package/dist/src/codegen/generator/expressions/primary.d.ts +3 -1
  28. package/dist/src/codegen/generator/expressions/primary.js +99 -1
  29. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  30. package/dist/src/codegen/generator/generator.js.map +1 -1
  31. package/dist/src/codegen/generator/module.js +25 -15
  32. package/dist/src/codegen/generator/module.js.map +1 -1
  33. package/dist/src/codegen/generator/state.d.ts +15 -0
  34. package/dist/src/codegen/generator/state.js +28 -1
  35. package/dist/src/codegen/generator/state.js.map +1 -1
  36. package/dist/src/codegen/generator/statements/control-flow.d.ts +3 -1
  37. package/dist/src/codegen/generator/statements/control-flow.js +85 -0
  38. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  39. package/dist/src/codegen/generator/statements/dispatch.js +15 -6
  40. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  41. package/dist/src/codegen/generator/statements/loops.d.ts +3 -0
  42. package/dist/src/codegen/generator/statements/loops.js +25 -11
  43. package/dist/src/codegen/generator/statements/loops.js.map +1 -1
  44. package/dist/src/codegen/generator/tables.d.ts +7 -0
  45. package/dist/src/codegen/generator/tables.js +41 -1
  46. package/dist/src/codegen/generator/tables.js.map +1 -1
  47. package/dist/src/driver/compiler.d.ts +1 -1
  48. package/dist/src/driver/compiler.js +33 -1
  49. package/dist/src/driver/compiler.js.map +1 -1
  50. package/dist/src/driver/modules.d.ts +4 -3
  51. package/dist/src/driver/modules.js +61 -4
  52. package/dist/src/driver/modules.js.map +1 -1
  53. package/dist/src/driver/toolchain.js +1 -1
  54. package/dist/src/driver/toolchain.js.map +1 -1
  55. package/dist/src/lexer/scanner.js.map +1 -1
  56. package/dist/src/parser/declarations/classes.js +4 -0
  57. package/dist/src/parser/declarations/classes.js.map +1 -1
  58. package/dist/src/parser/declarations/type-members.js.map +1 -1
  59. package/dist/src/parser/expressions.d.ts +2 -1
  60. package/dist/src/parser/expressions.js +10 -1
  61. package/dist/src/parser/expressions.js.map +1 -1
  62. package/dist/src/parser/helpers.js +2 -2
  63. package/dist/src/parser/helpers.js.map +1 -1
  64. package/dist/src/parser/literals.js +79 -6
  65. package/dist/src/parser/literals.js.map +1 -1
  66. package/dist/src/parser/modules.js.map +1 -1
  67. package/dist/src/parser/parser.js +0 -1
  68. package/dist/src/parser/parser.js.map +1 -1
  69. package/dist/src/parser/statements.d.ts +1 -0
  70. package/dist/src/parser/statements.js +13 -1
  71. package/dist/src/parser/statements.js.map +1 -1
  72. package/dist/src/parser/types.js +2 -2
  73. package/dist/src/parser/types.js.map +1 -1
  74. package/dist/tests/ast/factory.test.d.ts +1 -0
  75. package/dist/tests/ast/factory.test.js +35 -0
  76. package/dist/tests/ast/factory.test.js.map +1 -0
  77. package/dist/tests/ast/visitor.test.d.ts +1 -0
  78. package/dist/tests/ast/visitor.test.js +53 -0
  79. package/dist/tests/ast/visitor.test.js.map +1 -0
  80. package/dist/tests/cli/main.test.js +109 -9
  81. package/dist/tests/cli/main.test.js.map +1 -1
  82. package/dist/tests/codegen/closures.test.d.ts +1 -0
  83. package/dist/tests/codegen/closures.test.js +32 -0
  84. package/dist/tests/codegen/closures.test.js.map +1 -0
  85. package/dist/tests/codegen/expressions.test.d.ts +1 -0
  86. package/dist/tests/codegen/expressions.test.js +149 -0
  87. package/dist/tests/codegen/expressions.test.js.map +1 -0
  88. package/dist/tests/codegen/llvm.test.js +14 -0
  89. package/dist/tests/codegen/llvm.test.js.map +1 -1
  90. package/dist/tests/codegen/values.test.d.ts +1 -0
  91. package/dist/tests/codegen/values.test.js +48 -0
  92. package/dist/tests/codegen/values.test.js.map +1 -0
  93. package/dist/tests/diagnostics/diagnostic.test.d.ts +1 -0
  94. package/dist/tests/diagnostics/diagnostic.test.js +117 -0
  95. package/dist/tests/diagnostics/diagnostic.test.js.map +1 -0
  96. package/dist/tests/diagnostics/source.test.d.ts +1 -0
  97. package/dist/tests/diagnostics/source.test.js +58 -0
  98. package/dist/tests/diagnostics/source.test.js.map +1 -0
  99. package/dist/tests/driver/modules.test.d.ts +1 -0
  100. package/dist/tests/driver/modules.test.js +140 -0
  101. package/dist/tests/driver/modules.test.js.map +1 -0
  102. package/dist/tests/driver/paths.test.js +17 -1
  103. package/dist/tests/driver/paths.test.js.map +1 -1
  104. package/dist/tests/driver/runtime-lib.test.d.ts +1 -0
  105. package/dist/tests/driver/runtime-lib.test.js +19 -0
  106. package/dist/tests/driver/runtime-lib.test.js.map +1 -0
  107. package/dist/tests/driver/toolchain-provider.test.js +91 -0
  108. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  109. package/dist/tests/driver/toolchain.test.js +11 -1
  110. package/dist/tests/driver/toolchain.test.js.map +1 -1
  111. package/dist/tests/e2e/compile-node.test.js +14 -1
  112. package/dist/tests/e2e/compile-node.test.js.map +1 -1
  113. package/dist/tests/e2e/compile.test.js +465 -2
  114. package/dist/tests/e2e/compile.test.js.map +1 -1
  115. package/dist/tests/e2e/differential.test.d.ts +7 -0
  116. package/dist/tests/e2e/differential.test.js +581 -0
  117. package/dist/tests/e2e/differential.test.js.map +1 -0
  118. package/dist/tests/e2e/harness.d.ts +21 -0
  119. package/dist/tests/e2e/harness.js +26 -0
  120. package/dist/tests/e2e/harness.js.map +1 -1
  121. package/dist/tests/extensions/node.test.d.ts +1 -0
  122. package/dist/tests/extensions/node.test.js +32 -0
  123. package/dist/tests/extensions/node.test.js.map +1 -0
  124. package/dist/tests/lexer/identifiers.test.d.ts +1 -0
  125. package/dist/tests/lexer/identifiers.test.js +13 -0
  126. package/dist/tests/lexer/identifiers.test.js.map +1 -0
  127. package/dist/tests/lexer/numbers.test.d.ts +1 -0
  128. package/dist/tests/lexer/numbers.test.js +23 -0
  129. package/dist/tests/lexer/numbers.test.js.map +1 -0
  130. package/dist/tests/lexer/strings.test.d.ts +1 -0
  131. package/dist/tests/lexer/strings.test.js +72 -0
  132. package/dist/tests/lexer/strings.test.js.map +1 -0
  133. package/dist/tests/parser/declarations.test.d.ts +1 -0
  134. package/dist/tests/parser/declarations.test.js +70 -0
  135. package/dist/tests/parser/declarations.test.js.map +1 -0
  136. package/dist/tests/parser/expressions.test.d.ts +1 -0
  137. package/dist/tests/parser/expressions.test.js +47 -0
  138. package/dist/tests/parser/expressions.test.js.map +1 -0
  139. package/dist/tests/parser/helpers.test.d.ts +1 -0
  140. package/dist/tests/parser/helpers.test.js +58 -0
  141. package/dist/tests/parser/helpers.test.js.map +1 -0
  142. package/dist/tests/parser/literals.test.d.ts +1 -0
  143. package/dist/tests/parser/literals.test.js +70 -0
  144. package/dist/tests/parser/literals.test.js.map +1 -0
  145. package/dist/tests/parser/module-syntax.test.d.ts +1 -0
  146. package/dist/tests/parser/module-syntax.test.js +91 -0
  147. package/dist/tests/parser/module-syntax.test.js.map +1 -0
  148. package/dist/tests/parser/statements.test.d.ts +1 -0
  149. package/dist/tests/parser/statements.test.js +85 -0
  150. package/dist/tests/parser/statements.test.js.map +1 -0
  151. package/dist/tests/parser/type-members.test.d.ts +1 -0
  152. package/dist/tests/parser/type-members.test.js +86 -0
  153. package/dist/tests/parser/type-members.test.js.map +1 -0
  154. package/dist/tests/parser/types.test.d.ts +1 -0
  155. package/dist/tests/parser/types.test.js +143 -0
  156. package/dist/tests/parser/types.test.js.map +1 -0
  157. package/package.json +6 -1
  158. package/runtime/ext_node/fs/fs_ops.c +36 -1
  159. package/runtime/rt.h +17 -0
  160. package/runtime/rt_internal.h +88 -2
  161. package/runtime/xt_builtins.c +174 -6
  162. package/runtime/xt_containers.c +234 -42
  163. package/runtime/xt_generator.c +304 -0
  164. package/runtime/xt_io.c +170 -30
  165. package/runtime/xt_promise.c +6 -1
  166. package/runtime/xt_regex.h +105 -35
  167. package/runtime/xt_stdlib/dispatch.inc +255 -4
  168. package/runtime/xt_stdlib/strings.inc +17 -13
  169. package/runtime/xt_stdlib2/arrays.inc +126 -19
  170. package/runtime/xt_stdlib2/date.inc +9 -2
  171. package/runtime/xt_stdlib2/error.inc +162 -12
  172. package/runtime/xt_stdlib2/helpers.inc +4 -0
  173. package/runtime/xt_stdlib2/json.inc +19 -6
  174. package/runtime/xt_stdlib2/number-string-statics.inc +1 -1
  175. package/runtime/xt_stdlib2/numbers.inc +4 -5
  176. package/runtime/xt_stdlib2/objects.inc +29 -0
  177. package/runtime/xt_stdlib2/regexp-match.inc +293 -51
  178. package/runtime/xt_stdlib2/set.inc +58 -2
  179. package/runtime/xt_stdlib2/strings.inc +38 -0
  180. package/runtime/xt_symbol.c +149 -0
  181. package/runtime/xt_values.c +221 -15
  182. package/scripts/build-runtime.ts +1 -1
  183. package/src/ast/expressions.ts +8 -0
  184. package/src/binder/binder.ts +1 -1
  185. package/src/binder/references.ts +47 -4
  186. package/src/binder/types.ts +4 -0
  187. package/src/cli/main.ts +1 -2
  188. package/src/codegen/generator/calls/access.ts +158 -13
  189. package/src/codegen/generator/calls/closures.ts +24 -0
  190. package/src/codegen/generator/calls/invocation.ts +11 -1
  191. package/src/codegen/generator/calls/literals.ts +10 -4
  192. package/src/codegen/generator/calls/super.ts +6 -9
  193. package/src/codegen/generator/context.ts +44 -7
  194. package/src/codegen/generator/expressions/assignment.ts +1 -3
  195. package/src/codegen/generator/expressions/primary.ts +108 -2
  196. package/src/codegen/generator/generator.ts +0 -1
  197. package/src/codegen/generator/module.ts +24 -18
  198. package/src/codegen/generator/state.ts +44 -1
  199. package/src/codegen/generator/statements/control-flow.ts +86 -0
  200. package/src/codegen/generator/statements/dispatch.ts +18 -6
  201. package/src/codegen/generator/statements/loops.ts +29 -11
  202. package/src/codegen/generator/tables.ts +44 -1
  203. package/src/driver/compiler.ts +32 -2
  204. package/src/driver/modules.ts +64 -4
  205. package/src/driver/toolchain.ts +1 -1
  206. package/src/lexer/scanner.ts +0 -1
  207. package/src/parser/declarations/classes.ts +4 -0
  208. package/src/parser/declarations/type-members.ts +0 -2
  209. package/src/parser/expressions.ts +13 -1
  210. package/src/parser/helpers.ts +2 -2
  211. package/src/parser/literals.ts +82 -7
  212. package/src/parser/modules.ts +0 -1
  213. package/src/parser/parser.ts +0 -1
  214. package/src/parser/statements.ts +14 -0
  215. package/src/parser/types.ts +2 -2
@@ -1,6 +1,7 @@
1
- /* Compile `regexp` and search `input` starting at byte offset `start`.
2
- * Returns 1 and fills `matchStart`/`matchEnd` (absolute offsets into `input`)
3
- * when a match is found, otherwise 0. */
1
+ /* RegExp support built on POSIX ERE. JavaScript patterns are translated to
2
+ * their POSIX equivalents, capturing groups are tracked so the JS capture
3
+ * indices stay correct even after `(?:` groups are lowered, and the resulting
4
+ * `regmatch_t` offsets are converted back to offsets into the input string. */
4
5
 
5
6
  static int xt_regex_hex_digit(char c) {
6
7
  if (c >= '0' && c <= '9') return c - '0';
@@ -11,14 +12,25 @@ static int xt_regex_hex_digit(char c) {
11
12
 
12
13
  /* JavaScript regexes use escapes POSIX ERE does not understand (`\r`, `\n`,
13
14
  * `\d`, `\w`, `\s`, `(?:...)`, ...). Rewrite the common ones into their POSIX
14
- * equivalents before handing the pattern to the engine. Returns a malloc'd,
15
- * NUL-terminated string the caller must free. */
16
- static char *xt_regexp_translate_js(const char *pattern) {
15
+ * equivalents before handing the pattern to the engine.
16
+ *
17
+ * When `jsGroups` is non-NULL it receives, for each POSIX capturing group
18
+ * (1-based, stored at `jsGroups[i - 1]`), the JavaScript capture index, or -1
19
+ * for a group that was non-capturing in the source (POSIX has no
20
+ * non-capturing groups, so `(?:...)` is emitted as a capturing group).
21
+ * `*groupCount` receives the number of groups seen. `jsGroups` must have room
22
+ * for at least `strlen(pattern) + 1` entries.
23
+ *
24
+ * Returns a malloc'd, NUL-terminated string the caller must free. */
25
+ static char *xt_regexp_translate_js(const char *pattern, int *jsGroups, int *groupCount) {
17
26
  size_t length = strlen(pattern);
18
27
  size_t capacity = length * 16 + 16;
19
28
  char *out = (char *)malloc(capacity);
20
29
  if (!out) abort();
21
30
  size_t written = 0;
31
+ int posixGroup = 0;
32
+ int jsCapture = 1;
33
+ int inClass = 0;
22
34
  for (size_t i = 0; i < length; i++) {
23
35
  char c = pattern[i];
24
36
  const char *replacement = NULL;
@@ -46,12 +58,21 @@ static char *xt_regexp_translate_js(const char *pattern) {
46
58
  }
47
59
  default: break;
48
60
  }
49
- } else if (c == '(' && i + 2 < length && pattern[i + 1] == '?' && pattern[i + 2] == ':') {
61
+ } else if (c == '[') {
62
+ inClass = 1;
63
+ } else if (c == ']') {
64
+ inClass = 0;
65
+ } else if (!inClass && c == '(' && i + 2 < length && pattern[i + 1] == '?' && pattern[i + 2] == ':') {
50
66
  /* POSIX ERE has no non-capturing groups; a capturing group matches the
51
67
  * same text, only the capture bookkeeping differs. */
52
68
  literal = '(';
53
69
  consume = 2;
54
70
  }
71
+ if (!inClass && c == '(') {
72
+ if (jsGroups) jsGroups[posixGroup] = (consume == 2) ? -1 : jsCapture;
73
+ if (consume != 2) jsCapture++;
74
+ posixGroup++;
75
+ }
55
76
  if (replacement) {
56
77
  size_t addition = strlen(replacement);
57
78
  if (written + addition + 1 > capacity) { capacity = (written + addition) * 2 + 16; out = (char *)realloc(out, capacity); if (!out) abort(); }
@@ -67,20 +88,29 @@ static char *xt_regexp_translate_js(const char *pattern) {
67
88
  i += (size_t)consume;
68
89
  }
69
90
  out[written] = '\0';
91
+ if (groupCount) *groupCount = posixGroup;
70
92
  return out;
71
93
  }
72
94
 
73
- int xt_regexp_find(xt_value regexpValue, xt_value input, int32_t start, int32_t *matchStart, int32_t *matchEnd) {
95
+ /* Run `regexpValue` against `input` starting at byte offset `start`. On a
96
+ * match, fills `matches` (up to `nmatch` entries, offsets made absolute) and
97
+ * returns 1; otherwise returns 0 and leaves `matches` untouched. When
98
+ * `jsGroups` is non-NULL it receives the JS capture index for each POSIX
99
+ * group, and `*groupCount` the number of POSIX groups. */
100
+ static int xt_regexp_run(xt_value regexpValue, xt_value input, int32_t start,
101
+ regmatch_t *matches, size_t nmatch,
102
+ int *jsGroups, int *groupCount) {
74
103
  xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(regexpValue);
75
104
  xt_string *pattern = xt_as_string(regexp->source);
76
- xt_string *text = xt_as_string(xt_to_string(input));
105
+ xt_value textValue = xt_to_string(input);
106
+ xt_string *text = xt_as_string(textValue);
77
107
  if (!pattern || !text) return 0;
78
108
  if (start < 0) start = 0;
79
109
  if ((uint32_t)start > text->length) return 0;
80
110
  int flags = REG_EXTENDED;
81
111
  xt_string *f = xt_as_string(regexp->flags);
82
112
  if (f) for (uint32_t i = 0; i < f->length; i++) if (f->data[i] == 'i') flags |= REG_ICASE;
83
- char *translated = xt_regexp_translate_js(pattern->data);
113
+ char *translated = xt_regexp_translate_js(pattern->data, jsGroups, groupCount);
84
114
  if (!translated) return 0;
85
115
  regex_t compiled;
86
116
  if (regcomp(&compiled, translated, flags) != 0) {
@@ -88,14 +118,23 @@ int xt_regexp_find(xt_value regexpValue, xt_value input, int32_t start, int32_t
88
118
  return 0;
89
119
  }
90
120
  free(translated);
91
- regmatch_t match;
92
- match.rm_so = 0;
93
- match.rm_eo = 0;
94
- int ok = regexec(&compiled, text->data + start, 1, &match, 0) == 0;
121
+ int ok = regexec(&compiled, text->data + start, nmatch, matches, 0) == 0;
95
122
  regfree(&compiled);
96
123
  if (!ok) return 0;
97
- if (matchStart) *matchStart = start + (int32_t)match.rm_so;
98
- if (matchEnd) *matchEnd = start + (int32_t)match.rm_eo;
124
+ for (size_t i = 0; i < nmatch; i++) {
125
+ if (matches[i].rm_so >= 0) {
126
+ matches[i].rm_so += start;
127
+ matches[i].rm_eo += start;
128
+ }
129
+ }
130
+ return 1;
131
+ }
132
+
133
+ int xt_regexp_find(xt_value regexpValue, xt_value input, int32_t start, int32_t *matchStart, int32_t *matchEnd) {
134
+ regmatch_t match;
135
+ if (!xt_regexp_run(regexpValue, input, start, &match, 1, NULL, NULL)) return 0;
136
+ if (matchStart) *matchStart = (int32_t)match.rm_so;
137
+ if (matchEnd) *matchEnd = (int32_t)match.rm_eo;
99
138
  return 1;
100
139
  }
101
140
 
@@ -107,78 +146,281 @@ static int xt_regexp_is_global(xt_value regexpValue) {
107
146
  return 0;
108
147
  }
109
148
 
149
+ /* Number of JavaScript capturing groups for the pattern. */
150
+ static int xt_regexp_js_group_count(xt_value regexpValue) {
151
+ xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(regexpValue);
152
+ xt_string *pattern = xt_as_string(regexp->source);
153
+ if (!pattern) return 0;
154
+ int count = (int)pattern->length + 1;
155
+ int *groups = (int *)malloc(sizeof(int) * (size_t)count);
156
+ int groupCount = 0;
157
+ char *translated = xt_regexp_translate_js(pattern->data, groups, &groupCount);
158
+ free(translated);
159
+ int total = 0;
160
+ for (int i = 0; i < groupCount; i++) if (groups[i] > total) total = groups[i];
161
+ free(groups);
162
+ return total;
163
+ }
164
+
110
165
  static xt_value xt_regexp_test(xt_value target, xt_value input) {
111
166
  return xt_bool(xt_regexp_find(target, input, 0, NULL, NULL));
112
167
  }
113
168
 
114
- /* JavaScript-style match object: `[0]` is the matched text, `index` is the
115
- * match offset and `input` is the searched string. */
169
+ /* JavaScript-style match object: `[0]` is the matched text, `[1..n]` the
170
+ * capture groups, `index` is the match offset and `input` the searched string. */
116
171
  xt_value xt_regexp_exec(xt_value target, xt_value input) {
117
- int32_t start = 0;
118
- int32_t end = 0;
119
- if (!xt_regexp_find(target, input, 0, &start, &end)) return XT_NULL;
172
+ xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(target);
173
+ xt_string *pattern = xt_as_string(regexp->source);
174
+ size_t cap = (pattern ? pattern->length : 0) + 2;
175
+ regmatch_t *matches = (regmatch_t *)malloc(sizeof(regmatch_t) * cap);
176
+ int *groups = (int *)malloc(sizeof(int) * cap);
177
+ if (!matches || !groups) { free(matches); free(groups); return XT_NULL; }
120
178
  xt_value textValue = xt_to_string(input);
121
179
  xt_string *text = xt_as_string(textValue);
122
- xt_value out = xt_object_new();
123
- xt_object_set(out, xt_string_from_cstr("0"), xt_string_new(text->data + start, (int64_t)(end - start)));
124
- xt_object_set(out, xt_string_from_cstr("index"), xt_number((double)start));
125
- xt_object_set(out, xt_string_from_cstr("input"), textValue);
126
- xt_object_set(out, xt_string_from_cstr("length"), xt_number(1));
180
+ int groupCount = 0;
181
+ if (!xt_regexp_run(target, input, 0, matches, cap, groups, &groupCount)) {
182
+ free(matches);
183
+ free(groups);
184
+ return XT_NULL;
185
+ }
186
+ int jsTotal = 0;
187
+ for (int i = 0; i < groupCount; i++) if (groups[i] > jsTotal) jsTotal = groups[i];
188
+ xt_value out = xt_array_new(0, NULL);
189
+ xt_array_push(out, xt_string_new(text->data + matches[0].rm_so, (int64_t)(matches[0].rm_eo - matches[0].rm_so)));
190
+ for (int g = 1; g <= jsTotal; g++) {
191
+ xt_value groupValue = XT_UNDEFINED;
192
+ for (int i = 0; i < groupCount; i++) {
193
+ if (groups[i] != g) continue;
194
+ if (matches[i + 1].rm_so >= 0) {
195
+ groupValue = xt_string_new(text->data + matches[i + 1].rm_so, (int64_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
196
+ }
197
+ break;
198
+ }
199
+ xt_array_push(out, groupValue);
200
+ }
201
+ xt_set(out, xt_string_from_cstr("index"), xt_number((double)matches[0].rm_so));
202
+ xt_set(out, xt_string_from_cstr("input"), textValue);
203
+ xt_set(out, xt_string_from_cstr("groups"), XT_UNDEFINED);
204
+ free(matches);
205
+ free(groups);
127
206
  return out;
128
207
  }
129
208
 
209
+ /* `String.prototype.match`: a non-global regexp yields the exec result, a
210
+ * global one yields the array of matched substrings (or null). */
211
+ xt_value xt_regexp_match(xt_value input, xt_value regexp) {
212
+ if (!xt_regexp_is_global(regexp)) return xt_regexp_exec(regexp, input);
213
+ xt_value textValue = xt_to_string(input);
214
+ xt_string *text = xt_as_string(textValue);
215
+ xt_value results = xt_array_new(0, NULL);
216
+ int32_t cursor = 0;
217
+ int found = 0;
218
+ while (cursor <= (int32_t)text->length) {
219
+ int32_t start = 0;
220
+ int32_t end = 0;
221
+ if (!xt_regexp_find(regexp, input, cursor, &start, &end)) break;
222
+ found = 1;
223
+ xt_array_push(results, xt_string_new(text->data + start, (int64_t)(end - start)));
224
+ cursor = end > start ? end : end + 1;
225
+ }
226
+ return found ? results : XT_NULL;
227
+ }
228
+
229
+ /* -- replacement expansion ------------------------------------------------- */
230
+
231
+ static void xt_regexp_append(char **out, size_t *length, size_t *capacity, const char *data, size_t count) {
232
+ if (*length + count + 1 > *capacity) {
233
+ *capacity = (*length + count + 1) * 2 + 16;
234
+ *out = (char *)realloc(*out, *capacity);
235
+ if (!*out) abort();
236
+ }
237
+ memcpy(*out + *length, data, count);
238
+ *length += count;
239
+ }
240
+
241
+ static void xt_regexp_append_group(char **out, size_t *length, size_t *capacity,
242
+ xt_string *text, regmatch_t *matches, int *groups,
243
+ int groupCount, int group) {
244
+ for (int i = 0; i < groupCount; i++) {
245
+ if (groups[i] != group) continue;
246
+ if (matches[i + 1].rm_so >= 0) {
247
+ xt_regexp_append(out, length, capacity, text->data + matches[i + 1].rm_so,
248
+ (size_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
249
+ }
250
+ return;
251
+ }
252
+ }
253
+
254
+ static void xt_regexp_expand_replacement(char **out, size_t *length, size_t *capacity,
255
+ xt_string *replacement, xt_string *text,
256
+ regmatch_t *matches, int *groups, int groupCount,
257
+ int jsTotal) {
258
+ size_t start = (size_t)matches[0].rm_so;
259
+ size_t end = (size_t)matches[0].rm_eo;
260
+ for (size_t i = 0; i < replacement->length; i++) {
261
+ char c = replacement->data[i];
262
+ if (c != '$' || i + 1 >= replacement->length) {
263
+ xt_regexp_append(out, length, capacity, &c, 1);
264
+ continue;
265
+ }
266
+ char next = replacement->data[i + 1];
267
+ if (next == '$') {
268
+ xt_regexp_append(out, length, capacity, "$", 1);
269
+ i++;
270
+ } else if (next == '&') {
271
+ xt_regexp_append(out, length, capacity, text->data + start, end - start);
272
+ i++;
273
+ } else if (next == '`') {
274
+ xt_regexp_append(out, length, capacity, text->data, start);
275
+ i++;
276
+ } else if (next == '\'') {
277
+ xt_regexp_append(out, length, capacity, text->data + end, text->length - end);
278
+ i++;
279
+ } else if (next >= '0' && next <= '9') {
280
+ int value = next - '0';
281
+ int digits = 1;
282
+ if (i + 2 < replacement->length && replacement->data[i + 2] >= '0' && replacement->data[i + 2] <= '9') {
283
+ int two = value * 10 + (replacement->data[i + 2] - '0');
284
+ if (two <= jsTotal) { value = two; digits = 2; }
285
+ }
286
+ if (value >= 1 && value <= jsTotal) {
287
+ xt_regexp_append_group(out, length, capacity, text, matches, groups, groupCount, value);
288
+ i += (size_t)digits;
289
+ } else {
290
+ xt_regexp_append(out, length, capacity, "$", 1);
291
+ }
292
+ } else {
293
+ xt_regexp_append(out, length, capacity, "$", 1);
294
+ }
295
+ }
296
+ }
297
+
130
298
  /* `String.prototype.replace`/`replaceAll` with a RegExp search value. */
131
299
  xt_value xt_regexp_replace(xt_value value, xt_value regexpValue, xt_value replacementValue) {
132
300
  xt_string *text = xt_as_string(xt_to_string(value));
133
301
  if (!text) return value;
134
- xt_string *replacement = xt_as_string(xt_to_string(replacementValue));
135
- if (!replacement) return value;
136
302
  int global = xt_regexp_is_global(regexpValue);
137
- size_t capacity = text->length + replacement->length + 16;
303
+ int useFunction = XT_IS_FUNCTION(replacementValue);
304
+ xt_string *replacement = NULL;
305
+ if (!useFunction) {
306
+ replacement = xt_as_string(xt_to_string(replacementValue));
307
+ if (!replacement) return value;
308
+ }
309
+ xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(regexpValue);
310
+ xt_string *pattern = xt_as_string(regexp->source);
311
+ size_t cap = (pattern ? pattern->length : 0) + 2;
312
+ regmatch_t *matches = (regmatch_t *)malloc(sizeof(regmatch_t) * cap);
313
+ int *groups = (int *)malloc(sizeof(int) * cap);
314
+ if (!matches || !groups) { free(matches); free(groups); return value; }
315
+ size_t capacity = text->length + 64;
138
316
  char *out = (char *)malloc(capacity);
139
- if (!out) abort();
317
+ if (!out) { free(matches); free(groups); return value; }
140
318
  size_t length = 0;
141
319
  int32_t cursor = 0;
142
320
  int replaced = 0;
321
+ int jsTotal = xt_regexp_js_group_count(regexpValue);
143
322
  for (;;) {
144
- int32_t start = 0;
145
- int32_t end = 0;
146
- if (!xt_regexp_find(regexpValue, value, cursor, &start, &end)) break;
147
- size_t prefix = (size_t)(start - cursor);
148
- size_t needed = length + prefix + replacement->length + 2;
149
- if (needed > capacity) {
150
- capacity = needed * 2;
151
- out = (char *)realloc(out, capacity);
152
- if (!out) abort();
323
+ int groupCount = 0;
324
+ if (!xt_regexp_run(regexpValue, value, cursor, matches, cap, groups, &groupCount)) break;
325
+ int32_t start = (int32_t)matches[0].rm_so;
326
+ int32_t end = (int32_t)matches[0].rm_eo;
327
+ xt_regexp_append(&out, &length, &capacity, text->data + cursor, (size_t)(start - cursor));
328
+ if (useFunction) {
329
+ xt_value *args = (xt_value *)malloc(sizeof(xt_value) * (size_t)(jsTotal + 3));
330
+ args[0] = xt_string_new(text->data + start, (int64_t)(end - start));
331
+ for (int g = 1; g <= jsTotal; g++) {
332
+ xt_value groupValue = XT_UNDEFINED;
333
+ for (int i = 0; i < groupCount; i++) {
334
+ if (groups[i] != g) continue;
335
+ if (matches[i + 1].rm_so >= 0) {
336
+ groupValue = xt_string_new(text->data + matches[i + 1].rm_so, (int64_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
337
+ }
338
+ break;
339
+ }
340
+ args[g] = groupValue;
341
+ }
342
+ args[jsTotal + 1] = xt_number((double)start);
343
+ args[jsTotal + 2] = xt_to_string(value);
344
+ xt_value produced = xt_closure_call(replacementValue, jsTotal + 3, args);
345
+ free(args);
346
+ xt_string *producedText = xt_as_string(xt_to_string(produced));
347
+ if (producedText) xt_regexp_append(&out, &length, &capacity, producedText->data, producedText->length);
348
+ } else {
349
+ xt_regexp_expand_replacement(&out, &length, &capacity, replacement, text, matches, groups, groupCount, jsTotal);
153
350
  }
154
- memcpy(out + length, text->data + cursor, prefix);
155
- length += prefix;
156
- memcpy(out + length, replacement->data, replacement->length);
157
- length += replacement->length;
158
351
  replaced = 1;
159
352
  if (end > start) {
160
353
  cursor = end;
161
354
  } else {
162
355
  /* Zero-width match: copy one byte so the scan always progresses. */
163
356
  if (cursor >= (int32_t)text->length) break;
164
- out[length++] = text->data[cursor++];
357
+ xt_regexp_append(&out, &length, &capacity, text->data + cursor, 1);
358
+ cursor++;
165
359
  }
166
360
  if (!global) break;
167
361
  }
168
362
  if (!replaced) {
169
363
  free(out);
364
+ free(matches);
365
+ free(groups);
170
366
  return value;
171
367
  }
172
368
  size_t tail = text->length - (size_t)cursor;
173
- if (length + tail + 1 > capacity) {
174
- capacity = length + tail + 1;
175
- out = (char *)realloc(out, capacity);
176
- if (!out) abort();
177
- }
178
- memcpy(out + length, text->data + cursor, tail);
179
- length += tail;
369
+ xt_regexp_append(&out, &length, &capacity, text->data + cursor, tail);
180
370
  xt_value result = xt_string_new(out, (int64_t)length);
181
371
  free(out);
372
+ free(matches);
373
+ free(groups);
374
+ return result;
375
+ }
376
+
377
+ /* `String.prototype.split` with a RegExp separator. Capture groups are
378
+ * inserted into the result, matching the specification. */
379
+ xt_value xt_regexp_split(xt_value value, xt_value regexpValue, xt_value limitValue) {
380
+ xt_value result = xt_array_new(0, NULL);
381
+ xt_string *text = xt_as_string(xt_to_string(value));
382
+ if (!text) return result;
383
+ int64_t limit = limitValue == XT_UNDEFINED ? 0x7fffffff : (int64_t)xt_to_int32(limitValue);
384
+ if (limit <= 0) return result;
385
+ xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(regexpValue);
386
+ xt_string *pattern = xt_as_string(regexp->source);
387
+ size_t cap = (pattern ? pattern->length : 0) + 2;
388
+ regmatch_t *matches = (regmatch_t *)malloc(sizeof(regmatch_t) * cap);
389
+ int *groups = (int *)malloc(sizeof(int) * cap);
390
+ if (!matches || !groups) { free(matches); free(groups); return result; }
391
+ int jsTotal = xt_regexp_js_group_count(regexpValue);
392
+ int32_t len = (int32_t)text->length;
393
+ int32_t p = 0;
394
+ int32_t q = 0;
395
+ while (q < len) {
396
+ int groupCount = 0;
397
+ if (!xt_regexp_run(regexpValue, value, q, matches, cap, groups, &groupCount)) break;
398
+ int32_t start = (int32_t)matches[0].rm_so;
399
+ int32_t end = (int32_t)matches[0].rm_eo;
400
+ if (start != q) { q++; continue; }
401
+ if (end == p) { q++; continue; }
402
+ xt_array_push(result, xt_string_new(text->data + p, (int64_t)(q - p)));
403
+ if ((int64_t)xt_as_array(result)->length >= limit) { free(matches); free(groups); return result; }
404
+ for (int g = 1; g <= jsTotal; g++) {
405
+ xt_value groupValue = XT_UNDEFINED;
406
+ for (int i = 0; i < groupCount; i++) {
407
+ if (groups[i] != g) continue;
408
+ if (matches[i + 1].rm_so >= 0) {
409
+ groupValue = xt_string_new(text->data + matches[i + 1].rm_so, (int64_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
410
+ }
411
+ break;
412
+ }
413
+ xt_array_push(result, groupValue);
414
+ if ((int64_t)xt_as_array(result)->length >= limit) { free(matches); free(groups); return result; }
415
+ }
416
+ p = end;
417
+ q = p;
418
+ }
419
+ if ((int64_t)xt_as_array(result)->length < limit) {
420
+ xt_array_push(result, xt_string_new(text->data + p, (int64_t)(len - p)));
421
+ }
422
+ free(matches);
423
+ free(groups);
182
424
  return result;
183
425
  }
184
426
 
@@ -11,15 +11,71 @@ int32_t xt_set_size(xt_value value) {
11
11
 
12
12
  /* -- for...of iteration --------------------------------------------------- */
13
13
 
14
- /* `for...of` lowers to a length check plus an index lookup. Arrays and strings
15
- index directly; Maps yield `[key, value]` pairs and Sets yield their values. */
14
+ /* `for...of` lowers to a per-step check plus an index lookup. Arrays and
15
+ strings index directly; Maps yield `[key, value]` pairs and Sets yield their
16
+ values. Generators pull their next value during the check. */
16
17
  xt_value xt_iter_length(xt_value value) {
17
18
  if (xt_is_map(value)) return xt_number((double)xt_map_size(value));
18
19
  if (xt_is_set(value)) return xt_number((double)xt_set_size(value));
19
20
  return xt_array_length(value);
20
21
  }
21
22
 
23
+ /* Resolved `[Symbol.iterator]()` result: primes one `next()` per step and
24
+ * caches its `value` until `xt_iter_value` consumes it. */
25
+ typedef struct {
26
+ xt_header header;
27
+ xt_value iterator;
28
+ xt_value cached;
29
+ int32_t done;
30
+ } xt_iterator_state;
31
+
32
+ static int xt_is_iterator_state(xt_value value) {
33
+ if (!XT_IS_OBJECT(value)) return 0;
34
+ return ((xt_object *)XT_GET_PTR(value))->header.kind == XT_OBJECT_KIND_ITERATOR;
35
+ }
36
+
37
+ /* Resolve the iteration protocol once, before the loop starts. Arrays,
38
+ * strings, Maps, Sets and generators keep their built-in index protocol;
39
+ * any other object with a `Symbol.iterator` method is wrapped. */
40
+ xt_value xt_iter_open(xt_value value) {
41
+ if (XT_IS_ARRAY(value) || XT_IS_STRING(value) || xt_is_map(value) || xt_is_set(value) ||
42
+ xt_is_generator(value))
43
+ return value;
44
+ if (XT_IS_OBJECT(value)) {
45
+ xt_value method = xt_object_get(value, xt_symbol_well_known("iterator"));
46
+ if (XT_IS_FUNCTION(method)) {
47
+ xt_value iterator = xt_call_with_this(method, value, 0, NULL);
48
+ xt_iterator_state *state =
49
+ (xt_iterator_state *)xt_alloc(sizeof(xt_iterator_state), XT_OBJECT_KIND_ITERATOR);
50
+ state->iterator = iterator;
51
+ state->cached = XT_UNDEFINED;
52
+ state->done = 0;
53
+ return XT_FROM_PTR(XT_TAG_OBJECT, state);
54
+ }
55
+ }
56
+ xt_throw(xt_string_from_cstr("TypeError: value is not iterable"));
57
+ return XT_UNDEFINED;
58
+ }
59
+
60
+ xt_value xt_iter_has(xt_value value, xt_value index) {
61
+ if (xt_is_iterator_state(value)) {
62
+ xt_iterator_state *state = (xt_iterator_state *)XT_GET_PTR(value);
63
+ if (state->done) return XT_FALSE;
64
+ xt_value next = xt_get(state->iterator, xt_string_from_cstr("next"));
65
+ xt_value result = xt_call_with_this(next, state->iterator, 0, NULL);
66
+ state->cached = xt_get(result, xt_string_from_cstr("value"));
67
+ state->done = xt_truthy(xt_get(result, xt_string_from_cstr("done")));
68
+ return xt_bool(!state->done);
69
+ }
70
+ if (xt_is_generator(value)) return xt_bool(xt_generator_has_next(value));
71
+ if (xt_is_map(value)) return xt_bool(xt_to_int32(index) < xt_map_size(value));
72
+ if (xt_is_set(value)) return xt_bool(xt_to_int32(index) < xt_set_size(value));
73
+ return xt_bool(xt_to_int32(index) < xt_to_int32(xt_array_length(value)));
74
+ }
75
+
22
76
  xt_value xt_iter_value(xt_value value, xt_value index) {
77
+ if (xt_is_iterator_state(value)) return ((xt_iterator_state *)XT_GET_PTR(value))->cached;
78
+ if (xt_is_generator(value)) return xt_generator_iter_value(value);
23
79
  if (xt_is_map(value)) {
24
80
  xt_map *map = (xt_map *)XT_GET_PTR(value);
25
81
  int64_t i = (int64_t)xt_to_int32(index);
@@ -75,6 +75,44 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
75
75
  if (strcmp(method, "trimStart") == 0) return xt_string_trim_side(target, 1);
76
76
  if (strcmp(method, "trimEnd") == 0) return xt_string_trim_side(target, 0);
77
77
  if (strcmp(method, "replaceAll") == 0) return xt_string_replace_all(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
78
+ if (strcmp(method, "match") == 0) return xt_regexp_match(target, xt_arg_at(argc, argv, 0));
79
+ if (strcmp(method, "split") == 0) {
80
+ xt_value separator = xt_arg_at(argc, argv, 0);
81
+ if (xt_is_regexp(separator)) return xt_regexp_split(target, separator, xt_arg_at(argc, argv, 1));
82
+ *handled = 0;
83
+ return XT_UNDEFINED;
84
+ }
85
+ if (strcmp(method, "search") == 0) {
86
+ xt_value regexp = xt_arg_at(argc, argv, 0);
87
+ if (xt_is_regexp(regexp)) {
88
+ int32_t start = 0;
89
+ if (xt_regexp_find(regexp, target, 0, &start, NULL)) return xt_number((double)start);
90
+ return xt_number(-1);
91
+ }
92
+ *handled = 0;
93
+ return XT_UNDEFINED;
94
+ }
95
+ if (strcmp(method, "replaceAll") == 0) {
96
+ xt_value searchValue = xt_arg_at(argc, argv, 0);
97
+ if (xt_is_regexp(searchValue)) return xt_regexp_replace(target, searchValue, xt_arg_at(argc, argv, 1));
98
+ *handled = 0;
99
+ return XT_UNDEFINED;
100
+ }
101
+ if (strcmp(method, "lastIndexOf") == 0) {
102
+ xt_string *s = xt_as_string(target);
103
+ xt_string *needle = xt_as_string(xt_to_string(xt_arg_at(argc, argv, 0)));
104
+ if (!s || !needle) return xt_number(-1);
105
+ int32_t limit = (argc >= 2 && argv[1] != XT_UNDEFINED) ? xt_to_int32(argv[1]) : (int32_t)s->length;
106
+ if (limit > (int32_t)s->length) limit = (int32_t)s->length;
107
+ int32_t nlen = (int32_t)needle->length;
108
+ if (nlen == 0) return xt_number((double)(limit < 0 ? 0 : limit));
109
+ int32_t i = limit;
110
+ if (i > (int32_t)s->length - nlen) i = (int32_t)s->length - nlen;
111
+ for (; i >= 0; i--) {
112
+ if (memcmp(s->data + i, needle->data, (size_t)nlen) == 0) return xt_number((double)i);
113
+ }
114
+ return xt_number(-1);
115
+ }
78
116
  if (strcmp(method, "localeCompare") == 0) {
79
117
  xt_string *a = xt_as_string(target);
80
118
  xt_string *b = xt_as_string(xt_to_string(xt_arg_at(argc, argv, 0)));