@scriptc/runtime 0.0.3 → 0.0.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.
- package/package.json +1 -1
- package/src/scr_array.c +6 -12
- package/src/scr_assert.c +1 -2
- package/src/scr_bytes.c +3 -6
- package/src/scr_bytes_io.c +1 -2
- package/src/scr_closure.c +1 -2
- package/src/scr_console.c +5 -0
- package/src/scr_cycle.c +1 -2
- package/src/scr_error.c +2 -4
- package/src/scr_events_emitter.c +1 -2
- package/src/scr_exception.c +87 -2
- package/src/scr_inspect.c +1 -2
- package/src/scr_island.c +6 -6
- package/src/scr_json.c +11 -20
- package/src/scr_lib.c +60 -66
- package/src/scr_library.c +222 -0
- package/src/scr_map.c +2 -4
- package/src/scr_path.c +4 -8
- package/src/scr_qs.c +496 -0
- package/src/scr_regex.c +16 -25
- package/src/scr_runtime.h +136 -0
- package/src/scr_string.c +17 -6
- package/src/scr_symbol.c +2 -3
- package/src/scr_url.c +9 -16
- package/src/scr_url_params.c +6 -12
- package/src/scr_zlib.c +1 -2
- package/vendor/mbedtls/library/ecp.c +1 -1
package/src/scr_qs.c
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/* node:querystring (scr_runtime.h has the API contract): Node v24's
|
|
2
|
+
* legacy query-string codec — NOT URLSearchParams (the escaping and '+'
|
|
3
|
+
* rules differ; that surface lives in scr_url_params.c). LINK-GATED:
|
|
4
|
+
* compiled into the binary only when the IR uses the qs surface
|
|
5
|
+
* (moduleUsesQs — the scr_url_params gating precedent), so qs-free
|
|
6
|
+
* programs pay zero bytes. querystring.escape never reaches this unit at
|
|
7
|
+
* all: Node's qsEscape encodes exactly the component unreserved set, so
|
|
8
|
+
* the frontend lowers it to the always-linked
|
|
9
|
+
* scr_str_encode_uri_component.
|
|
10
|
+
*
|
|
11
|
+
* Everything here is a quirk-faithful port of lib/querystring.js (Node is
|
|
12
|
+
* the oracle): parse's interleaved sep/eq scan with its naive
|
|
13
|
+
* partial-match resets and the encodeCheck fast path, unescapeBuffer's
|
|
14
|
+
* UTF-16-code-unit byte truncation, and stringify's encodeStringified
|
|
15
|
+
* value rules. The scans run byte-wise over the runtime's UTF-8 storage —
|
|
16
|
+
* equivalent to Node's code-unit scans for well-formed input, because the
|
|
17
|
+
* machine only assigns meaning to ASCII ('%', '+', hex, and the sep/eq
|
|
18
|
+
* sequences positionally) and UTF-8 multibyte alignment mirrors UTF-16
|
|
19
|
+
* unit alignment. */
|
|
20
|
+
#include "scr_runtime.h"
|
|
21
|
+
|
|
22
|
+
#include <math.h>
|
|
23
|
+
#include <stdio.h>
|
|
24
|
+
#include <stdlib.h>
|
|
25
|
+
#include <string.h>
|
|
26
|
+
|
|
27
|
+
/* ── a tiny growable byte buffer (scr_url_params.c's, redeclared) ────── */
|
|
28
|
+
|
|
29
|
+
typedef struct {
|
|
30
|
+
char *data;
|
|
31
|
+
size_t len;
|
|
32
|
+
size_t cap;
|
|
33
|
+
} QsBuf;
|
|
34
|
+
|
|
35
|
+
static void qb_init(QsBuf *b) {
|
|
36
|
+
b->cap = 64;
|
|
37
|
+
b->len = 0;
|
|
38
|
+
b->data = malloc(b->cap);
|
|
39
|
+
if (!b->data) {
|
|
40
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
41
|
+
abort();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static void qb_append(QsBuf *b, const char *bytes, size_t n) {
|
|
46
|
+
if (b->len + n > b->cap) {
|
|
47
|
+
while (b->len + n > b->cap) b->cap *= 2;
|
|
48
|
+
char *grown = realloc(b->data, b->cap);
|
|
49
|
+
if (!grown) {
|
|
50
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
51
|
+
abort();
|
|
52
|
+
}
|
|
53
|
+
b->data = grown;
|
|
54
|
+
}
|
|
55
|
+
memcpy(b->data + b->len, bytes, n);
|
|
56
|
+
b->len += n;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static void qb_push(QsBuf *b, char c) { qb_append(b, &c, 1); }
|
|
60
|
+
|
|
61
|
+
static void qb_append_str(QsBuf *b, const ScrStr *s) {
|
|
62
|
+
qb_append(b, s->data, s->len);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static ScrStr *qb_take(QsBuf *b) {
|
|
66
|
+
ScrStr *s = scr_str_new(b->data, b->len);
|
|
67
|
+
free(b->data);
|
|
68
|
+
return s;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ── shared scan helpers ─────────────────────────────────────────────── */
|
|
72
|
+
|
|
73
|
+
static int qs_unhex(unsigned c) {
|
|
74
|
+
if (c >= '0' && c <= '9') return (int)(c - '0');
|
|
75
|
+
if (c >= 'A' && c <= 'F') return (int)(c - 'A' + 10);
|
|
76
|
+
if (c >= 'a' && c <= 'f') return (int)(c - 'a' + 10);
|
|
77
|
+
return -1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* One UTF-8 code point at p (well-formed by the runtime invariant);
|
|
81
|
+
* *adv gets the byte length. */
|
|
82
|
+
static uint32_t qs_utf8_decode(const char *p, size_t *adv) {
|
|
83
|
+
unsigned char b0 = (unsigned char)p[0];
|
|
84
|
+
if (b0 < 0x80) {
|
|
85
|
+
*adv = 1;
|
|
86
|
+
return b0;
|
|
87
|
+
}
|
|
88
|
+
if (b0 < 0xE0) {
|
|
89
|
+
*adv = 2;
|
|
90
|
+
return (uint32_t)((b0 & 0x1Fu) << 6) | ((unsigned char)p[1] & 0x3Fu);
|
|
91
|
+
}
|
|
92
|
+
if (b0 < 0xF0) {
|
|
93
|
+
*adv = 3;
|
|
94
|
+
return (uint32_t)((b0 & 0x0Fu) << 12) |
|
|
95
|
+
(uint32_t)(((unsigned char)p[1] & 0x3Fu) << 6) |
|
|
96
|
+
((unsigned char)p[2] & 0x3Fu);
|
|
97
|
+
}
|
|
98
|
+
*adv = 4;
|
|
99
|
+
return (uint32_t)((b0 & 0x07u) << 18) |
|
|
100
|
+
(uint32_t)(((unsigned char)p[1] & 0x3Fu) << 12) |
|
|
101
|
+
(uint32_t)(((unsigned char)p[2] & 0x3Fu) << 6) |
|
|
102
|
+
((unsigned char)p[3] & 0x3Fu);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static void qs_utf8_encode(QsBuf *b, uint32_t cp) {
|
|
106
|
+
char tmp[4];
|
|
107
|
+
if (cp < 0x80) {
|
|
108
|
+
tmp[0] = (char)cp;
|
|
109
|
+
qb_append(b, tmp, 1);
|
|
110
|
+
} else if (cp < 0x800) {
|
|
111
|
+
tmp[0] = (char)(0xC0 | (cp >> 6));
|
|
112
|
+
tmp[1] = (char)(0x80 | (cp & 0x3F));
|
|
113
|
+
qb_append(b, tmp, 2);
|
|
114
|
+
} else if (cp < 0x10000) {
|
|
115
|
+
tmp[0] = (char)(0xE0 | (cp >> 12));
|
|
116
|
+
tmp[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
117
|
+
tmp[2] = (char)(0x80 | (cp & 0x3F));
|
|
118
|
+
qb_append(b, tmp, 3);
|
|
119
|
+
} else {
|
|
120
|
+
tmp[0] = (char)(0xF0 | (cp >> 18));
|
|
121
|
+
tmp[1] = (char)(0x80 | ((cp >> 12) & 0x3F));
|
|
122
|
+
tmp[2] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
123
|
+
tmp[3] = (char)(0x80 | (cp & 0x3F));
|
|
124
|
+
qb_append(b, tmp, 4);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* ── unescape (Node's qsUnescape) ────────────────────────────────────── */
|
|
129
|
+
|
|
130
|
+
/* The string's UTF-16 code units (astral chars split into surrogate
|
|
131
|
+
* pairs) — unescapeBuffer's scan is defined over these, byte-truncation
|
|
132
|
+
* quirk included, so the fallback builds them first. Caller frees. */
|
|
133
|
+
static uint16_t *qs_utf16_units(const ScrStr *s, size_t *out_n) {
|
|
134
|
+
/* One unit per byte is a safe cap (ASCII 1:1; multibyte shrinks). */
|
|
135
|
+
uint16_t *units = malloc(sizeof(uint16_t) * (s->len ? s->len : 1));
|
|
136
|
+
if (!units) {
|
|
137
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
138
|
+
abort();
|
|
139
|
+
}
|
|
140
|
+
size_t n = 0, i = 0;
|
|
141
|
+
while (i < s->len) {
|
|
142
|
+
size_t adv;
|
|
143
|
+
uint32_t cp = qs_utf8_decode(s->data + i, &adv);
|
|
144
|
+
i += adv;
|
|
145
|
+
if (cp >= 0x10000) {
|
|
146
|
+
cp -= 0x10000;
|
|
147
|
+
units[n++] = (uint16_t)(0xD800 + (cp >> 10));
|
|
148
|
+
units[n++] = (uint16_t)(0xDC00 + (cp & 0x3FF));
|
|
149
|
+
} else {
|
|
150
|
+
units[n++] = (uint16_t)cp;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
*out_n = n;
|
|
154
|
+
return units;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* WHATWG UTF-8 decode with replacement (maximal subpart) — the semantics
|
|
158
|
+
* of Buffer.prototype.toString('utf8') the fallback path ends with. */
|
|
159
|
+
static ScrStr *qs_utf8_replace_decode(const unsigned char *p, size_t n) {
|
|
160
|
+
QsBuf out;
|
|
161
|
+
qb_init(&out);
|
|
162
|
+
size_t i = 0;
|
|
163
|
+
while (i < n) {
|
|
164
|
+
unsigned char b0 = p[i];
|
|
165
|
+
if (b0 < 0x80) {
|
|
166
|
+
qb_push(&out, (char)b0);
|
|
167
|
+
i++;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
int need;
|
|
171
|
+
unsigned lo = 0x80, hi = 0xBF;
|
|
172
|
+
if (b0 >= 0xC2 && b0 <= 0xDF) need = 1;
|
|
173
|
+
else if (b0 == 0xE0) { need = 2; lo = 0xA0; }
|
|
174
|
+
else if (b0 == 0xED) { need = 2; hi = 0x9F; }
|
|
175
|
+
else if (b0 >= 0xE1 && b0 <= 0xEF) need = 2;
|
|
176
|
+
else if (b0 == 0xF0) { need = 3; lo = 0x90; }
|
|
177
|
+
else if (b0 == 0xF4) { need = 3; hi = 0x8F; }
|
|
178
|
+
else if (b0 >= 0xF1 && b0 <= 0xF3) need = 3;
|
|
179
|
+
else { /* 0x80..0xC1, 0xF5..0xFF: never a leading byte */
|
|
180
|
+
qs_utf8_encode(&out, 0xFFFD);
|
|
181
|
+
i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
/* Consume as many valid continuations as exist (maximal subpart):
|
|
185
|
+
* an invalid or missing continuation replaces the consumed prefix
|
|
186
|
+
* with ONE U+FFFD and rescans at the offending byte. */
|
|
187
|
+
uint32_t cp = b0 & (uint32_t)(0xFF >> (need + 2));
|
|
188
|
+
size_t j = i + 1;
|
|
189
|
+
int k = 0;
|
|
190
|
+
bool ok = true;
|
|
191
|
+
for (; k < need; k++, j++) {
|
|
192
|
+
if (j >= n) { ok = false; break; }
|
|
193
|
+
unsigned c = p[j];
|
|
194
|
+
unsigned clo = (k == 0) ? lo : 0x80, chi = (k == 0) ? hi : 0xBF;
|
|
195
|
+
if (c < clo || c > chi) { ok = false; break; }
|
|
196
|
+
cp = (cp << 6) | (c & 0x3F);
|
|
197
|
+
}
|
|
198
|
+
if (!ok) {
|
|
199
|
+
qs_utf8_encode(&out, 0xFFFD);
|
|
200
|
+
i = j; /* prefix consumed; rescan at the offending byte */
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
qs_utf8_encode(&out, cp);
|
|
204
|
+
i = j;
|
|
205
|
+
}
|
|
206
|
+
return qb_take(&out);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* Node's unescapeBuffer(s) (decodeSpaces false — neither qsUnescape nor
|
|
210
|
+
* the exported unescape passes it) over the UTF-16 units, then the
|
|
211
|
+
* replacement decode of the byte buffer. */
|
|
212
|
+
static ScrStr *qs_unescape_buffer(const ScrStr *s) {
|
|
213
|
+
size_t n;
|
|
214
|
+
uint16_t *units = qs_utf16_units(s, &n);
|
|
215
|
+
unsigned char *out = malloc(n ? n : 1);
|
|
216
|
+
if (!out) {
|
|
217
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
218
|
+
abort();
|
|
219
|
+
}
|
|
220
|
+
size_t w = 0, index = 0;
|
|
221
|
+
/* maxLength = s.length - 2 as a signed comparison (n < 2 must refuse
|
|
222
|
+
* every escape, matching Node's `index < maxLength` on negatives). */
|
|
223
|
+
while (index < n) {
|
|
224
|
+
unsigned cur = units[index];
|
|
225
|
+
if (cur == '%' && n >= 3 && index < n - 2) {
|
|
226
|
+
unsigned c1 = units[index + 1];
|
|
227
|
+
int hexHigh = (c1 < 256) ? qs_unhex(c1) : -1;
|
|
228
|
+
if (hexHigh < 0) {
|
|
229
|
+
out[w++] = '%';
|
|
230
|
+
index++;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
unsigned c2 = units[index + 2];
|
|
234
|
+
int hexLow = (c2 < 256) ? qs_unhex(c2) : -1;
|
|
235
|
+
if (hexLow < 0) {
|
|
236
|
+
out[w++] = '%';
|
|
237
|
+
index++; /* Node: ++index then index-- — net one step */
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
cur = (unsigned)(hexHigh * 16 + hexLow);
|
|
241
|
+
index += 2;
|
|
242
|
+
}
|
|
243
|
+
out[w++] = (unsigned char)(cur & 0xFF); /* Buffer write truncates */
|
|
244
|
+
index++;
|
|
245
|
+
}
|
|
246
|
+
free(units);
|
|
247
|
+
ScrStr *res = qs_utf8_replace_decode(out, w);
|
|
248
|
+
free(out);
|
|
249
|
+
return res;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
ScrStr *scr_qs_unescape(const ScrStr *s) {
|
|
253
|
+
ScrStr *strict = scr_str_decode_uri_component_try((ScrStr *)s);
|
|
254
|
+
if (strict) return strict;
|
|
255
|
+
return qs_unescape_buffer(s);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* ── parse ───────────────────────────────────────────────────────────── */
|
|
259
|
+
|
|
260
|
+
/* addKeyVal's tail: decode-if-encoded, then group into the overflow map
|
|
261
|
+
* (first value = the str_tag arm; a repeat REPLACES it with a two-element
|
|
262
|
+
* string[] in the arr_tag arm; later repeats push). key/value move in. */
|
|
263
|
+
static void qs_add_key_val(ScrMap *out, ScrStr *key, ScrStr *value,
|
|
264
|
+
bool key_encoded, bool val_encoded,
|
|
265
|
+
uint32_t str_tag, uint32_t arr_tag) {
|
|
266
|
+
if (key->len > 0 && key_encoded) {
|
|
267
|
+
ScrStr *dec = scr_qs_unescape(key);
|
|
268
|
+
scr_str_release(key);
|
|
269
|
+
key = dec;
|
|
270
|
+
}
|
|
271
|
+
if (value->len > 0 && val_encoded) {
|
|
272
|
+
ScrStr *dec = scr_qs_unescape(value);
|
|
273
|
+
scr_str_release(value);
|
|
274
|
+
value = dec;
|
|
275
|
+
}
|
|
276
|
+
ScrUnion *cell = scr_map_get_str_ref(out, key);
|
|
277
|
+
if (!cell) {
|
|
278
|
+
scr_map_set_str_ref(out, key,
|
|
279
|
+
scr_union_new_ref(str_tag, value, &scr_str_retain_v,
|
|
280
|
+
&scr_str_release_v, NULL));
|
|
281
|
+
scr_str_release(key);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (cell->tag == str_tag) {
|
|
285
|
+
/* obj[key] = [curValue, value] */
|
|
286
|
+
ScrArr *rows = scr_arr_new(SCR_ELEM_STR, 2);
|
|
287
|
+
scr_arr_push_ref(rows, scr_str_retain_v(scr_union_peek(cell)));
|
|
288
|
+
scr_arr_push_ref(rows, value);
|
|
289
|
+
scr_map_set_str_ref(out, key,
|
|
290
|
+
scr_union_new_ref(arr_tag, rows, &scr_arr_retain_v,
|
|
291
|
+
&scr_arr_release_v, NULL));
|
|
292
|
+
} else {
|
|
293
|
+
scr_arr_push_ref((ScrArr *)scr_union_peek(cell), value);
|
|
294
|
+
}
|
|
295
|
+
scr_union_release(cell);
|
|
296
|
+
scr_str_release(key);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
void scr_qs_parse_into(ScrMap *out, const ScrStr *qs, const ScrStr *sep,
|
|
300
|
+
const ScrStr *eq, double max_keys, uint32_t str_tag,
|
|
301
|
+
uint32_t arr_tag) {
|
|
302
|
+
if (qs->len == 0) return;
|
|
303
|
+
/* Node's falsy rule: undefined/null/'' all mean the default. */
|
|
304
|
+
const char *sep_b = (sep && sep->len) ? sep->data : "&";
|
|
305
|
+
size_t sep_len = (sep && sep->len) ? sep->len : 1;
|
|
306
|
+
const char *eq_b = (eq && eq->len) ? eq->data : "=";
|
|
307
|
+
size_t eq_len = (eq && eq->len) ? eq->len : 1;
|
|
308
|
+
|
|
309
|
+
/* pairs: Node's `maxKeys > 0 ? maxKeys : -1` as a double so Infinity
|
|
310
|
+
* decrements forever, exactly like Node's -1 sentinel. */
|
|
311
|
+
double pairs = max_keys > 0 ? max_keys : -1;
|
|
312
|
+
|
|
313
|
+
const char *p = qs->data;
|
|
314
|
+
size_t n = qs->len;
|
|
315
|
+
QsBuf key, value;
|
|
316
|
+
qb_init(&key);
|
|
317
|
+
qb_init(&value);
|
|
318
|
+
size_t last_pos = 0, sep_idx = 0, eq_idx = 0;
|
|
319
|
+
bool key_encoded = false, val_encoded = false;
|
|
320
|
+
int encode_check = 0;
|
|
321
|
+
bool returned = false;
|
|
322
|
+
|
|
323
|
+
for (size_t i = 0; i < n; ++i) {
|
|
324
|
+
unsigned char code = (unsigned char)p[i];
|
|
325
|
+
|
|
326
|
+
/* Try matching the pair separator (e.g. '&'). */
|
|
327
|
+
if (code == (unsigned char)sep_b[sep_idx]) {
|
|
328
|
+
if (++sep_idx == sep_len) {
|
|
329
|
+
/* Key/value pair separator match. */
|
|
330
|
+
size_t end = i - sep_idx + 1;
|
|
331
|
+
if (eq_idx < eq_len) {
|
|
332
|
+
/* No (entire) key/value separator seen. */
|
|
333
|
+
if (last_pos < end) {
|
|
334
|
+
qb_append(&key, p + last_pos, end - last_pos);
|
|
335
|
+
} else if (key.len == 0) {
|
|
336
|
+
/* An empty substring between separators. */
|
|
337
|
+
if (--pairs == 0) { returned = true; break; }
|
|
338
|
+
last_pos = i + 1;
|
|
339
|
+
sep_idx = eq_idx = 0;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
} else if (last_pos < end) {
|
|
343
|
+
qb_append(&value, p + last_pos, end - last_pos);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
qs_add_key_val(out, scr_str_new(key.data, key.len),
|
|
347
|
+
scr_str_new(value.data, value.len), key_encoded,
|
|
348
|
+
val_encoded, str_tag, arr_tag);
|
|
349
|
+
|
|
350
|
+
if (--pairs == 0) { returned = true; break; }
|
|
351
|
+
key_encoded = val_encoded = false;
|
|
352
|
+
key.len = value.len = 0;
|
|
353
|
+
encode_check = 0;
|
|
354
|
+
last_pos = i + 1;
|
|
355
|
+
sep_idx = eq_idx = 0;
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
sep_idx = 0;
|
|
359
|
+
/* Try matching the key/value separator (e.g. '=') if we haven't. */
|
|
360
|
+
if (eq_idx < eq_len) {
|
|
361
|
+
if (code == (unsigned char)eq_b[eq_idx]) {
|
|
362
|
+
if (++eq_idx == eq_len) {
|
|
363
|
+
/* Key/value separator match. */
|
|
364
|
+
size_t end = i - eq_idx + 1;
|
|
365
|
+
if (last_pos < end) qb_append(&key, p + last_pos, end - last_pos);
|
|
366
|
+
encode_check = 0;
|
|
367
|
+
last_pos = i + 1;
|
|
368
|
+
}
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
eq_idx = 0;
|
|
372
|
+
if (!key_encoded) {
|
|
373
|
+
/* Match a valid encoded byte once, to minimize decode calls. */
|
|
374
|
+
if (code == '%') {
|
|
375
|
+
encode_check = 1;
|
|
376
|
+
continue;
|
|
377
|
+
} else if (encode_check > 0) {
|
|
378
|
+
if (qs_unhex(code) >= 0) {
|
|
379
|
+
if (++encode_check == 3) key_encoded = true;
|
|
380
|
+
continue;
|
|
381
|
+
} else {
|
|
382
|
+
encode_check = 0;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (code == '+') {
|
|
387
|
+
if (last_pos < i) qb_append(&key, p + last_pos, i - last_pos);
|
|
388
|
+
qb_push(&key, ' ');
|
|
389
|
+
last_pos = i + 1;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (code == '+') {
|
|
394
|
+
if (last_pos < i) qb_append(&value, p + last_pos, i - last_pos);
|
|
395
|
+
qb_push(&value, ' ');
|
|
396
|
+
last_pos = i + 1;
|
|
397
|
+
} else if (!val_encoded) {
|
|
398
|
+
if (code == '%') {
|
|
399
|
+
encode_check = 1;
|
|
400
|
+
} else if (encode_check > 0) {
|
|
401
|
+
if (qs_unhex(code) >= 0) {
|
|
402
|
+
if (++encode_check == 3) val_encoded = true;
|
|
403
|
+
} else {
|
|
404
|
+
encode_check = 0;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (!returned) {
|
|
412
|
+
/* Leftover key or value data. */
|
|
413
|
+
bool ended_empty = false;
|
|
414
|
+
if (last_pos < n) {
|
|
415
|
+
if (eq_idx < eq_len) qb_append(&key, p + last_pos, n - last_pos);
|
|
416
|
+
else if (sep_idx < sep_len) qb_append(&value, p + last_pos, n - last_pos);
|
|
417
|
+
} else if (eq_idx == 0 && key.len == 0) {
|
|
418
|
+
ended_empty = true; /* ended on an empty substring */
|
|
419
|
+
}
|
|
420
|
+
if (!ended_empty) {
|
|
421
|
+
qs_add_key_val(out, scr_str_new(key.data, key.len),
|
|
422
|
+
scr_str_new(value.data, value.len), key_encoded,
|
|
423
|
+
val_encoded, str_tag, arr_tag);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
free(key.data);
|
|
427
|
+
free(value.data);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* ── stringify ───────────────────────────────────────────────────────── */
|
|
431
|
+
|
|
432
|
+
/* Node's encodeStringified over one DOM value: strings escape, finite
|
|
433
|
+
* numbers render then escape, booleans are bare, everything else is the
|
|
434
|
+
* empty value. Appends to b. */
|
|
435
|
+
static void qs_stringify_value(QsBuf *b, const ScrDyn *v) {
|
|
436
|
+
switch (v->kind) {
|
|
437
|
+
case SCR_DYN_STR: {
|
|
438
|
+
ScrStr *enc = scr_str_encode_uri_component(v->v.str);
|
|
439
|
+
qb_append_str(b, enc);
|
|
440
|
+
scr_str_release(enc);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
case SCR_DYN_NUM: {
|
|
444
|
+
if (!isfinite(v->v.num)) return;
|
|
445
|
+
ScrStr *num = scr_f64_to_scrstr(v->v.num);
|
|
446
|
+
ScrStr *enc = scr_str_encode_uri_component(num);
|
|
447
|
+
qb_append_str(b, enc);
|
|
448
|
+
scr_str_release(enc);
|
|
449
|
+
scr_str_release(num);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
case SCR_DYN_BOOL:
|
|
453
|
+
if (v->v.b) qb_append(b, "true", 4);
|
|
454
|
+
else qb_append(b, "false", 5);
|
|
455
|
+
return;
|
|
456
|
+
default:
|
|
457
|
+
return; /* null/undefined/objects/arrays/functions → '' */
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
ScrStr *scr_qs_stringify(const ScrDyn *obj, const ScrStr *sep,
|
|
462
|
+
const ScrStr *eq) {
|
|
463
|
+
const char *sep_b = (sep && sep->len) ? sep->data : "&";
|
|
464
|
+
size_t sep_len = (sep && sep->len) ? sep->len : 1;
|
|
465
|
+
const char *eq_b = (eq && eq->len) ? eq->data : "=";
|
|
466
|
+
size_t eq_len = (eq && eq->len) ? eq->len : 1;
|
|
467
|
+
if (!obj || obj->kind != SCR_DYN_OBJ) return scr_str_new("", 0);
|
|
468
|
+
|
|
469
|
+
QsBuf fields;
|
|
470
|
+
qb_init(&fields);
|
|
471
|
+
/* JS own-key order (array-index keys ascending first) — ObjectKeys. */
|
|
472
|
+
ScrDyn *keys = scr_dyn_obj_keys((ScrDyn *)obj);
|
|
473
|
+
for (size_t i = 0; i < keys->v.arr.len; i++) {
|
|
474
|
+
const ScrDyn *kd = keys->v.arr.items[i];
|
|
475
|
+
const ScrStr *k = kd->v.str;
|
|
476
|
+
const ScrDyn *v = scr_dyn_obj_get(obj, k->data, k->len);
|
|
477
|
+
if (!v) continue; /* unreachable: keys came from the object */
|
|
478
|
+
ScrStr *ks = scr_str_encode_uri_component((ScrStr *)k);
|
|
479
|
+
if (v->kind == SCR_DYN_ARR) {
|
|
480
|
+
for (size_t j = 0; j < v->v.arr.len; j++) {
|
|
481
|
+
if (fields.len) qb_append(&fields, sep_b, sep_len);
|
|
482
|
+
qb_append_str(&fields, ks);
|
|
483
|
+
qb_append(&fields, eq_b, eq_len);
|
|
484
|
+
qs_stringify_value(&fields, v->v.arr.items[j]);
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
if (fields.len) qb_append(&fields, sep_b, sep_len);
|
|
488
|
+
qb_append_str(&fields, ks);
|
|
489
|
+
qb_append(&fields, eq_b, eq_len);
|
|
490
|
+
qs_stringify_value(&fields, v);
|
|
491
|
+
}
|
|
492
|
+
scr_str_release(ks);
|
|
493
|
+
}
|
|
494
|
+
scr_dyn_release(keys);
|
|
495
|
+
return qb_take(&fields);
|
|
496
|
+
}
|
package/src/scr_regex.c
CHANGED
|
@@ -37,8 +37,7 @@
|
|
|
37
37
|
#include "libregexp.h"
|
|
38
38
|
|
|
39
39
|
static void scr_regex_oom(void) {
|
|
40
|
-
|
|
41
|
-
abort();
|
|
40
|
+
scr_trap("scriptc: out of memory\n");
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
/* ── libregexp host hooks ─────────────────────────────────────────────
|
|
@@ -102,7 +101,7 @@ static void scr_note_compiled(ScrRegex *re) {
|
|
|
102
101
|
scr_compiled = realloc(scr_compiled, scr_compiled_cap * sizeof *scr_compiled);
|
|
103
102
|
if (!scr_compiled) scr_regex_oom();
|
|
104
103
|
}
|
|
105
|
-
if (scr_compiled_len == 0)
|
|
104
|
+
if (scr_compiled_len == 0) scr_atexit(scr_regex_free_bytecodes);
|
|
106
105
|
scr_compiled[scr_compiled_len++] = re;
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -119,9 +118,8 @@ static int scr_lre_flags(const ScrStr *flags) {
|
|
|
119
118
|
case 'u': mask |= LRE_FLAG_UNICODE; break;
|
|
120
119
|
case 'y': mask |= LRE_FLAG_STICKY; break;
|
|
121
120
|
default:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
abort();
|
|
121
|
+
scr_trap_fmt("scriptc: internal error: unexpected regex flag '%c'\n",
|
|
122
|
+
flags->data[i]);
|
|
125
123
|
}
|
|
126
124
|
}
|
|
127
125
|
return mask;
|
|
@@ -197,9 +195,8 @@ static uint8_t *scr_regex_bc(ScrRegex *re) {
|
|
|
197
195
|
* tsc's parser has already caught plain syntax errors, so this is
|
|
198
196
|
* rare). */
|
|
199
197
|
fflush(stdout);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
abort();
|
|
198
|
+
scr_trap_fmt("scriptc: SyntaxError: Invalid regular expression: /%s/%s: %s\n",
|
|
199
|
+
re->source->data, re->flags->data, error_msg);
|
|
203
200
|
}
|
|
204
201
|
re->bc = bc;
|
|
205
202
|
scr_note_compiled(re);
|
|
@@ -315,8 +312,7 @@ static int scr_exec(uint8_t **capture, const uint8_t *bc, const uint16_t *u,
|
|
|
315
312
|
int rc = lre_exec(capture, bc, (const uint8_t *)u, index, len, 1, lre_opaque());
|
|
316
313
|
if (rc < 0) {
|
|
317
314
|
fflush(stdout);
|
|
318
|
-
|
|
319
|
-
abort();
|
|
315
|
+
scr_trap("scriptc: regular expression execution failed\n");
|
|
320
316
|
}
|
|
321
317
|
return rc;
|
|
322
318
|
}
|
|
@@ -351,11 +347,9 @@ bool scr_regex_test(ScrRegex *re, ScrStr *s) {
|
|
|
351
347
|
* iteration this slice does not model (the frontend rejects the sites
|
|
352
348
|
* it can see; values that flow through variables land here). */
|
|
353
349
|
fflush(stdout);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
"replace/replaceAll/split\n");
|
|
358
|
-
abort();
|
|
350
|
+
scr_trap("scriptc: test() on a regex with the 'g' or 'y' flag is not "
|
|
351
|
+
"supported (stateful lastIndex); drop the flag, or use "
|
|
352
|
+
"replace/replaceAll/split\n");
|
|
359
353
|
}
|
|
360
354
|
int len;
|
|
361
355
|
uint16_t *u = scr_to_utf16(s, &len);
|
|
@@ -379,11 +373,9 @@ ScrArr *scr_regex_match(ScrStr *s, ScrRegex *re) {
|
|
|
379
373
|
uint8_t *bc = scr_regex_bc(re);
|
|
380
374
|
if (lre_get_flags(bc) & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
|
|
381
375
|
fflush(stdout);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
"flag, or use replace/replaceAll/split\n");
|
|
386
|
-
abort();
|
|
376
|
+
scr_trap("scriptc: match() on a regex with the 'g' or 'y' flag is not "
|
|
377
|
+
"supported (an every-match array is a different shape); drop the "
|
|
378
|
+
"flag, or use replace/replaceAll/split\n");
|
|
387
379
|
}
|
|
388
380
|
int len;
|
|
389
381
|
uint16_t *u = scr_to_utf16(s, &len);
|
|
@@ -803,7 +795,7 @@ static bool scr_assert_regex_hits(ScrRegex *re, ScrStr *s) {
|
|
|
803
795
|
static ScrStr *scr_assert_regex_render(ScrRegex *re) {
|
|
804
796
|
size_t cap = re->source->len + re->flags->len + 2;
|
|
805
797
|
char *buf = malloc(cap);
|
|
806
|
-
if (!buf)
|
|
798
|
+
if (!buf) scr_trap("scriptc: out of memory\n");
|
|
807
799
|
size_t n = 0;
|
|
808
800
|
buf[n++] = '/';
|
|
809
801
|
memcpy(buf + n, re->source->data, re->source->len);
|
|
@@ -831,7 +823,7 @@ static void scr_assert_regex_input_fail(bool negated, ScrRegex *re, ScrStr *inpu
|
|
|
831
823
|
const char *mid = ". Input:\n\n";
|
|
832
824
|
size_t cap = strlen(head) + rre->len + strlen(mid) + insp->len + 1;
|
|
833
825
|
char *buf = malloc(cap);
|
|
834
|
-
if (!buf)
|
|
826
|
+
if (!buf) scr_trap("scriptc: out of memory\n");
|
|
835
827
|
size_t n = 0;
|
|
836
828
|
memcpy(buf + n, head, strlen(head));
|
|
837
829
|
n += strlen(head);
|
|
@@ -924,8 +916,7 @@ ScrRegex *scr_regex_new(ScrStr *pattern, ScrStr *flags) {
|
|
|
924
916
|
}
|
|
925
917
|
ScrRegex *re = calloc(1, sizeof *re);
|
|
926
918
|
if (!re) {
|
|
927
|
-
|
|
928
|
-
abort();
|
|
919
|
+
scr_trap("scriptc: out of memory\n");
|
|
929
920
|
}
|
|
930
921
|
re->rc = 1;
|
|
931
922
|
re->source = pattern->len > 0 ? scr_str_retain(pattern) : scr_str_new("(?:)", 4);
|