From a00d95bd9d54aff0baa82647523e378baec2a261 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Wed, 15 Apr 2026 19:31:28 -0700 Subject: [PATCH] add regexp index group support --- examples/npm/djot/ant.lockb | Bin 0 -> 272 bytes examples/npm/djot/index.ts | 9 ++++ examples/npm/djot/package.json | 12 +++++ src/ant.c | 69 ++++++++++++++++++++----- src/modules/regex.c | 71 ++++++++++++++++++++++++++ tests/test_promise_inspect_long.cjs | 15 ++++++ tests/test_regexp_exec_fast_paths.cjs | 14 +++++ 7 files changed, 176 insertions(+), 14 deletions(-) create mode 100644 examples/npm/djot/ant.lockb create mode 100644 examples/npm/djot/index.ts create mode 100644 examples/npm/djot/package.json create mode 100644 tests/test_promise_inspect_long.cjs diff --git a/examples/npm/djot/ant.lockb b/examples/npm/djot/ant.lockb new file mode 100644 index 0000000000000000000000000000000000000000..01f268f51342cb081d2fb0c780cbbf5f35b62938 GIT binary patch literal 272 zcmeZq_YPoWU|;}Y1_lNO2Pke}U|`^2gkYE)NFE(Kq-5on=!1xil9GaAD}DW<)bz~a zlA=nzyn@`UV!iyLbbW*(eO<6xT?0L1JtMu6^eT{fTp$99nV~cjl!j4#&0OwzjGy-8 zRVyZ`?uukusB~(^duyG`$1N34?bUjgY`@VV%EI>Dyt4gYdUCD0U3al2o$O3e6k8O> g`_cIS=VKr{%wUEg(E$t$42dp|1>Yhr{R0UD0K9cPQ2+n{ literal 0 HcmV?d00001 diff --git a/examples/npm/djot/index.ts b/examples/npm/djot/index.ts new file mode 100644 index 0000000..cf85984 --- /dev/null +++ b/examples/npm/djot/index.ts @@ -0,0 +1,9 @@ +import djot from '@djot/djot'; + +const parsed = djot.parse('hi _there_'); +const ast = djot.renderAST(parsed); +const html = djot.renderHTML(parsed); + +console.log(parsed); +console.log(ast); +console.log(html); diff --git a/examples/npm/djot/package.json b/examples/npm/djot/package.json new file mode 100644 index 0000000..c85704e --- /dev/null +++ b/examples/npm/djot/package.json @@ -0,0 +1,12 @@ +{ + "name": "djot", + "type": "module", + "main": "index.ts", + "scripts": { + "start": "ant index.ts" + }, + "dependencies": { + "@djot/djot": "^0.3.2" + }, + "devDependencies": {} +} \ No newline at end of file diff --git a/src/ant.c b/src/ant.c index 6c8b3bd..88e4bba 100644 --- a/src/ant.c +++ b/src/ant.c @@ -1211,6 +1211,17 @@ ant_value_t js_inspect_builder_result(js_inspect_builder_t *builder) { return out; } +static inline char *js_inspect_builder_write_ptr(js_inspect_builder_t *builder, size_t *avail) { + if (!builder->buf || builder->len == 0) { + if (avail) *avail = 0; + return NULL; + } + + size_t write_index = builder->n < builder->len ? builder->n : builder->len - 1; + if (avail) *avail = builder->len - write_index; + return builder->buf + write_index; +} + static bool js_inspect_builder_reserve(js_inspect_builder_t *builder, size_t extra) { if (!builder->growable) return true; @@ -1238,7 +1249,10 @@ static bool js_inspect_append(js_inspect_builder_t *builder, const char *src, si return true; } - builder->n += cpy(builder->buf + builder->n, REMAIN(builder->n, builder->len), src, srclen); + size_t avail = 0; + char *dst = js_inspect_builder_write_ptr(builder, &avail); + builder->n += cpy(dst, avail, src, srclen); + return true; } @@ -1256,7 +1270,10 @@ js_inspect_vappendf(js_inspect_builder_t *builder, const char *fmt, va_list args return true; } - int needed = vsnprintf(builder->buf + builder->n, REMAIN(builder->n, builder->len), fmt, args); + size_t avail = 0; + char *dst = js_inspect_builder_write_ptr(builder, &avail); + int needed = vsnprintf(dst, avail, fmt, args); + if (needed < 0) return false; builder->n += (size_t)needed; @@ -1279,7 +1296,9 @@ static bool js_inspect_append_indent(js_inspect_builder_t *builder, int indent) static bool js_inspect_append_tostr(js_inspect_builder_t *builder, ant_value_t value) { if (!builder->growable) { - builder->n += tostr(builder->js, value, builder->buf + builder->n, REMAIN(builder->n, builder->len)); + size_t avail = 0; + char *dst = js_inspect_builder_write_ptr(builder, &avail); + builder->n += tostr(builder->js, value, dst, avail); return true; } @@ -1308,7 +1327,9 @@ static bool js_inspect_append_tostr(js_inspect_builder_t *builder, ant_value_t v static bool js_inspect_append_key_interned(js_inspect_builder_t *builder, const char *key, size_t klen) { if (!builder->growable) { - builder->n += strkey_interned(builder->js, key, klen, builder->buf + builder->n, REMAIN(builder->n, builder->len)); + size_t avail = 0; + char *dst = js_inspect_builder_write_ptr(builder, &avail); + builder->n += strkey_interned(builder->js, key, klen, dst, avail); return true; } @@ -2190,17 +2211,37 @@ static size_t strstring(ant_t *js, ant_value_t value, char *buf, size_t len) { ant_offset_t slen, off = vstr(js, value, &slen); const char *str = (const char *)(uintptr_t)off; size_t n = 0; - n += cpy(buf + n, REMAIN(n, len), "'", 1); - for (ant_offset_t i = 0; i < slen && n < len - 1; i++) { + + size_t avail = 0; + char *dst = NULL; + + dst = (len == 0) ? NULL : (buf + (n < len ? n : len - 1)); + avail = (len == 0) ? 0 : (len - (n < len ? n : len - 1)); + n += cpy(dst, avail, "'", 1); + + for (ant_offset_t i = 0; i < slen; i++) { char c = str[i]; - if (c == '\n') { n += cpy(buf + n, REMAIN(n, len), "\\n", 2); } - else if (c == '\r') { n += cpy(buf + n, REMAIN(n, len), "\\r", 2); } - else if (c == '\t') { n += cpy(buf + n, REMAIN(n, len), "\\t", 2); } - else if (c == '\\') { n += cpy(buf + n, REMAIN(n, len), "\\\\", 2); } - else if (c == '\'') { n += cpy(buf + n, REMAIN(n, len), "\\'", 2); } - else { if (n < len) buf[n++] = c; } - } - n += cpy(buf + n, REMAIN(n, len), "'", 1); + dst = (len == 0) ? NULL : (buf + (n < len ? n : len - 1)); + avail = (len == 0) ? 0 : (len - (n < len ? n : len - 1)); + + if (c == '\n') n += cpy(dst, avail, "\\n", 2); + else if (c == '\r') n += cpy(dst, avail, "\\r", 2); + else if (c == '\t') n += cpy(dst, avail, "\\t", 2); + else if (c == '\\') n += cpy(dst, avail, "\\\\", 2); + else if (c == '\'') n += cpy(dst, avail, "\\'", 2); + + else { + if (avail > 1) { + *dst = c; + dst[1] = '\0'; + } else if (avail == 1) *dst = '\0'; + n++; + } + } + + dst = (len == 0) ? NULL : (buf + (n < len ? n : len - 1)); + avail = (len == 0) ? 0 : (len - (n < len ? n : len - 1)); + n += cpy(dst, avail, "'", 1); return n; } diff --git a/src/modules/regex.c b/src/modules/regex.c index f0b8f06..f607060 100644 --- a/src/modules/regex.c +++ b/src/modules/regex.c @@ -781,11 +781,76 @@ static ant_value_t builtin_regexp_groups_getter(ant_t *js, ant_value_t *args, in return groups; } +static ant_value_t regexp_build_indices_pair(ant_t *js, PCRE2_SIZE start, PCRE2_SIZE end) { + if (start == PCRE2_UNSET) return js_mkundef(); + + ant_value_t pair = js_mkarr(js); + if (is_err(pair)) return pair; + js_arr_push(js, pair, tov((double)start)); + js_arr_push(js, pair, tov((double)end)); + + return pair; +} + +static ant_value_t regexp_build_indices_groups( + ant_t *js, + ant_value_t groups_meta, + ant_value_t indices_arr +) { + ant_value_t groups = js_mkobj(js); + if (is_err(groups)) return groups; + js_set_proto_init(groups, js_mknull()); + + for (ant_offset_t i = 0; ; i += 2) { + ant_value_t name = js_arr_get(js, groups_meta, i); + if (vtype(name) == T_UNDEF) break; + + ant_value_t index_val = js_arr_get(js, groups_meta, i + 1); + ant_offset_t index = (vtype(index_val) == T_NUM) ? (ant_offset_t)tod(index_val) : 0; + char idxstr[16]; + (void)uint_to_str(idxstr, sizeof(idxstr), (uint64_t)index); + + ant_value_t value = js_getprop_fallback(js, indices_arr, idxstr); + ant_offset_t name_len, name_off = vstr(js, name, &name_len); + ant_value_t status = setprop_cstr(js, groups, (const char *)(uintptr_t)name_off, (size_t)name_len, value); + if (is_err(status)) return status; + } + + return groups; +} + +static ant_value_t regexp_build_indices_result( + ant_t *js, + ant_value_t regexp, + PCRE2_SIZE *ovector, + uint32_t ovcount +) { + ant_value_t indices_arr = js_mkarr(js); + if (is_err(indices_arr)) return indices_arr; + + for (uint32_t i = 0; i < ovcount && i < 32; i++) { + ant_value_t pair = regexp_build_indices_pair(js, ovector[2*i], ovector[2*i+1]); + if (is_err(pair)) return pair; + js_arr_push(js, indices_arr, pair); + } + + ant_value_t groups_meta = js_get_slot(regexp, SLOT_REGEXP_NAMED_GROUPS); + if (is_object_type(groups_meta)) { + ant_value_t groups = regexp_build_indices_groups(js, groups_meta, indices_arr); + if (is_err(groups)) return groups; + if (is_err(setprop_cstr(js, indices_arr, "groups", 6, groups))) return js_mkerr(js, "oom"); + } else if (is_err(setprop_cstr(js, indices_arr, "groups", 6, js_mkundef()))) return js_mkerr(js, "oom"); + + return indices_arr; +} + static ant_value_t regexp_exec_internal(ant_t *js, ant_value_t regexp, ant_value_t str_arg, bool truthy_only) { ant_offset_t str_len, str_off = vstr(js, str_arg, &str_len); const char *str_ptr = (char *)(uintptr_t)(str_off); uint8_t flags_mask = regexp_flags_mask(js, regexp); + bool global_flag = (flags_mask & REGEXP_FLAG_GLOBAL) != 0; + bool has_indices = (flags_mask & REGEXP_FLAG_HAS_INDICES) != 0; bool sticky_flag = (flags_mask & REGEXP_FLAG_STICKY) != 0; // TODO: reduce nesting @@ -858,6 +923,12 @@ static ant_value_t regexp_exec_internal(ant_t *js, ant_value_t regexp, ant_value js_set_getter_desc(js, js_as_obj(result_arr), "groups", 6, js_mkfun(builtin_regexp_groups_getter), JS_DESC_E | JS_DESC_C); } else if (is_err(setprop_cstr(js, result_arr, "groups", 6, js_mkundef()))) return js_mkerr(js, "oom"); + if (has_indices) { + ant_value_t indices = regexp_build_indices_result(js, regexp, ovector, ovcount); + if (is_err(indices)) return indices; + if (is_err(setprop_cstr(js, result_arr, "indices", 7, indices))) return js_mkerr(js, "oom"); + } + return result_arr; } diff --git a/tests/test_promise_inspect_long.cjs b/tests/test_promise_inspect_long.cjs new file mode 100644 index 0000000..edbf928 --- /dev/null +++ b/tests/test_promise_inspect_long.cjs @@ -0,0 +1,15 @@ +const { inspect } = require('node:util'); + +function assert(cond, msg) { + if (!cond) throw new Error(msg); +} + +const longValue = '1234567890123456789012345678901234567890123456789012345678901234567890'; +const promise = Promise.resolve({ a: longValue, b: 2 }); +const rendered = inspect(promise); + +assert(rendered.includes(`a: '${longValue}'`), `expected full promise payload in inspect output, got: ${rendered}`); +assert(rendered.includes('b: 2'), `expected secondary property in inspect output, got: ${rendered}`); +assert(rendered.includes('Symbol(async_id):'), `expected promise metadata in inspect output, got: ${rendered}`); + +console.log('PASS'); diff --git a/tests/test_regexp_exec_fast_paths.cjs b/tests/test_regexp_exec_fast_paths.cjs index 015d47f..773ed4b 100644 --- a/tests/test_regexp_exec_fast_paths.cjs +++ b/tests/test_regexp_exec_fast_paths.cjs @@ -105,4 +105,18 @@ assert( 'RegExp.prototype.flags should read observable flag properties in spec order' ); +const indicesMatch = /(?a)(b)?/d.exec('ab'); +assert(indicesMatch !== null, 'expected hasIndices regexp to match'); +assert(Array.isArray(indicesMatch.indices), 'match.indices should be an array'); +assert(indicesMatch.indices.length === 3, 'match.indices should include the full match and captures'); +assert(indicesMatch.indices[0][0] === 0 && indicesMatch.indices[0][1] === 2, 'full match indices mismatch'); +assert(indicesMatch.indices[1][0] === 0 && indicesMatch.indices[1][1] === 1, 'first capture indices mismatch'); +assert(indicesMatch.indices[2][0] === 1 && indicesMatch.indices[2][1] === 2, 'second capture indices mismatch'); +assert(indicesMatch.indices.groups.word[0] === 0 && indicesMatch.indices.groups.word[1] === 1, + 'named capture indices mismatch'); + +const unmatchedIndices = /a(b)?/d.exec('a'); +assert(unmatchedIndices !== null, 'expected optional capture regexp to match'); +assert(unmatchedIndices.indices[1] === undefined, 'unmatched capture should expose undefined indices'); + console.log('regex exec fast path semantics ok'); -- 2.51.2