Support native function descriptors and RegExp string coercion (#563)

This commit is contained in:
Chris Tate
2026-09-28 21:37:17 -05:00
committed by GitHub
parent 24ab670305
commit e6ad714a51
11 changed files with 146 additions and 6 deletions
+2
View File
@@ -1615,6 +1615,8 @@ function emitStringExpr(
return emitter.newTemp(e.type, `scr_regex_source(${r.name})`);
case "flags":
return emitter.newTemp(e.type, `scr_regex_flags(${r.name})`);
case "toString":
return emitter.newTemp(e.type, `scr_regex_to_string(${r.name})`);
case "replace":
return emitter.newTemp(
e.type,
@@ -114,6 +114,12 @@ export function emitRegexIntrinsic(host: LlvmEmitterContext, e: IrExpr & { kind:
B.line(`${t} = call ptr @scr_regex_flags(ptr ${r.name})`);
return host.own({ name: t, type: e.type });
}
case "toString": {
host.declare(`declare ptr @scr_regex_to_string(ptr)`);
const t = B.tmp();
B.line(`${t} = call ptr @scr_regex_to_string(ptr ${r.name})`);
return host.own({ name: t, type: e.type });
}
case "replace": {
host.declare(`declare ptr @scr_regex_replace(ptr, ptr, ptr)`);
const t = B.tmp();
@@ -5532,6 +5532,9 @@ export function lowerEnvironmentKey(lowerer: Lowerer, node: ts.Expression): IrEx
export function ensureString(lowerer: Lowerer, e: IrExpr, node: ts.Node): IrExpr {
if (e.type.kind === "string") return e;
if (e.type.kind === "regex") {
return { kind: "regexIntrinsic", method: "toString", receiver: e, args: [], type: STRING, loc: e.loc };
}
if (e.type.kind === "bigint") {
return {
kind: "libCall",
+1
View File
@@ -1977,6 +1977,7 @@ export type IrRegexIntrinsicMethod =
| "search"
| "source"
| "flags"
| "toString"
| "replace"
| "replaceAll"
| "split";
+1
View File
@@ -73,6 +73,7 @@ export const REGEX_INTRINSIC_SIGS: Record<
search: { receiver: STRING, argTypes: [REGEX], result: F64 },
source: { receiver: REGEX, argTypes: [], result: STRING },
flags: { receiver: REGEX, argTypes: [], result: STRING },
toString: { receiver: REGEX, argTypes: [], result: STRING },
replace: { receiver: STRING, argTypes: [REGEX, STRING], result: STRING },
replaceAll: { receiver: STRING, argTypes: [REGEX, STRING], result: STRING },
split: { receiver: STRING, argTypes: [REGEX, F64], result: arrayOf(STRING) },
+6 -4
View File
@@ -1171,7 +1171,6 @@ ScrDyn *scr_dyn_define_props(ScrDyn *target, ScrDyn *descs) {
ScrDyn *pair = pending->v.arr.items[i];
ScrDyn *key = pair->v.arr.items[0];
ScrDyn *descriptor = pair->v.arr.items[1];
ScrStr *name = key->v.str;
if (target->kind == SCR_DYN_FUNC &&
(scr_dyn_obj_get(descriptor, "get", 3) || scr_dyn_obj_get(descriptor, "set", 3))) {
scr_throw_error_msg(SCR_ERR_ERROR,
@@ -1180,8 +1179,6 @@ ScrDyn *scr_dyn_define_props(ScrDyn *target, ScrDyn *descs) {
scr_dyn_release(pending);
return NULL;
}
ScrDyn *value = scr_dyn_obj_get(descriptor, "value", 5);
if (!value) value = scr_dyn_undefined();
if (target->kind == SCR_DYN_OBJ) {
ScrDyn *defined = scr_dyn_define_property(target, key, descriptor);
if (!defined) {
@@ -1197,8 +1194,13 @@ ScrDyn *scr_dyn_define_props(ScrDyn *target, ScrDyn *descs) {
target->v.fn.clo->props = box;
}
ScrDyn *table = (ScrDyn *)scr_box_get_ref(target->v.fn.clo->props); /* +1 */
scr_dyn_obj_set(table, name->data, name->len, scr_dyn_retain(value));
ScrDyn *defined = scr_dyn_define_property(table, key, descriptor);
scr_dyn_release(table);
if (!defined) {
scr_dyn_release(pending);
return NULL;
}
scr_dyn_release(defined);
}
}
scr_dyn_release(pending);
+36 -2
View File
@@ -869,6 +869,34 @@ bool scr_dyn_proxy_has(const ScrDyn *proxy, const ScrStr *key) {
ScrDyn *scr_dyn_own_descriptor(const ScrDyn *value, const ScrStr *key) {
if (scr_dyn_class_reflection_fence(value)) return NULL;
if (value->kind == SCR_DYN_FUNC) {
if (value->v.fn.clo->props) {
ScrDyn *table = (ScrDyn *)scr_box_get_ref(value->v.fn.clo->props);
ScrDyn *desc = table ? scr_dyn_own_descriptor(table, key) : NULL;
bool found = desc && desc->kind != SCR_DYN_UNDEF;
scr_dyn_release(table);
if (found || scr_exc_pending()) return desc;
scr_dyn_release(desc);
}
bool name = key->len == 4 && memcmp(key->data, "name", 4) == 0;
bool length = key->len == 6 && memcmp(key->data, "length", 6) == 0;
if (!name && !length) return scr_dyn_retain(scr_dyn_undefined());
ScrDyn *out = scr_dyn_new_obj();
ScrDyn *member = scr_dyn_fn_get(value, key->data, key->len);
scr_dyn_obj_set(out, "value", 5, member);
scr_dyn_obj_set(out, "writable", 8, scr_dyn_new_bool(false));
scr_dyn_obj_set(out, "enumerable", 10, scr_dyn_new_bool(false));
scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(true));
return out;
}
if (value->kind == SCR_DYN_ARR && key->len == 6 && memcmp(key->data, "length", 6) == 0) {
ScrDyn *out = scr_dyn_new_obj();
scr_dyn_obj_set(out, "value", 5, scr_dyn_new_num((double)value->v.arr.len));
scr_dyn_obj_set(out, "writable", 8, scr_dyn_new_bool(true));
scr_dyn_obj_set(out, "enumerable", 10, scr_dyn_new_bool(false));
scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(false));
return out;
}
if (value->kind == SCR_DYN_OBJ) {
ScrDynEntry *entry = scr_dyn_entry((ScrDyn *)value, key);
if (!entry) return scr_dyn_retain(scr_dyn_undefined());
@@ -1944,7 +1972,7 @@ ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key) {
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
return NULL;
}
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY) {
if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR) {
static const char msg[] = "Object.getOwnPropertyDescriptor on this value is not supported yet";
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
return NULL;
@@ -4692,7 +4720,13 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) {
if (v->kind == SCR_DYN_BYTES) {
return scr_dyn_canonical_own_index(key, v->v.bytes->len);
}
if (v->kind == SCR_DYN_FUNC || v->kind == SCR_DYN_HANDLE ||
if (v->kind == SCR_DYN_FUNC) {
ScrDyn *desc = scr_dyn_own_descriptor(v, key);
bool has = desc && desc->kind != SCR_DYN_UNDEF;
scr_dyn_release(desc);
return has;
}
if (v->kind == SCR_DYN_HANDLE ||
v->kind == SCR_DYN_TYPED_REF) {
const char *m = "Own-property checks on this checked-dynamic kind are not supported yet";
scr_throw_error_msg(SCR_ERR_ERROR, m, strlen(m));
+63
View File
@@ -222,6 +222,69 @@ void scr_regex_release_v(void *re) { scr_regex_release(re); }
ScrStr *scr_regex_source(ScrRegex *re) { return scr_str_retain(re->source); }
ScrStr *scr_regex_flags(ScrRegex *re) { return scr_str_retain(re->flags); }
ScrStr *scr_regex_to_string(ScrRegex *re) {
const char *source = re->source->data;
size_t len = re->source->len;
size_t escaped = 0;
bool in_class = false;
bool quoted = false;
for (size_t i = 0; i < len; i++) {
char c = source[i];
bool line_separator = i + 2 < len && (unsigned char)c == 0xE2 &&
(unsigned char)source[i + 1] == 0x80 &&
((unsigned char)source[i + 2] == 0xA8 ||
(unsigned char)source[i + 2] == 0xA9);
if (!quoted && c == '/' && !in_class) escaped++;
if (!quoted && (c == '\n' || c == '\r')) escaped++;
if (!quoted && line_separator) escaped += 3;
if (!quoted && c == '[') in_class = true;
else if (!quoted && c == ']') in_class = false;
if (c == '\\') quoted = !quoted;
else quoted = false;
}
size_t total = 2 + len + escaped + re->flags->len;
char *buf = malloc(total + 1);
if (!buf) scr_regex_oom();
size_t pos = 0;
buf[pos++] = '/';
in_class = false;
quoted = false;
for (size_t i = 0; i < len; i++) {
char c = source[i];
bool line_separator = i + 2 < len && (unsigned char)c == 0xE2 &&
(unsigned char)source[i + 1] == 0x80 &&
((unsigned char)source[i + 2] == 0xA8 ||
(unsigned char)source[i + 2] == 0xA9);
if (!quoted && c == '/' && !in_class) buf[pos++] = '\\';
if (!quoted && line_separator) {
buf[pos++] = '\\';
buf[pos++] = 'u';
buf[pos++] = '2';
buf[pos++] = '0';
buf[pos++] = '2';
buf[pos++] = source[i + 2] == (char)0xA8 ? '8' : '9';
i += 2;
quoted = false;
continue;
}
if (!quoted && (c == '\n' || c == '\r')) {
buf[pos++] = '\\';
c = c == '\n' ? 'n' : 'r';
}
buf[pos++] = c;
if (!quoted && c == '[') in_class = true;
else if (!quoted && c == ']') in_class = false;
if (source[i] == '\\') quoted = !quoted;
else quoted = false;
}
buf[pos++] = '/';
memcpy(buf + pos, re->flags->data, re->flags->len);
pos += re->flags->len;
buf[pos] = '\0';
ScrStr *out = scr_str_new(buf, pos);
free(buf);
return out;
}
/* ── UTF-8 ⇄ UTF-16 (the exec buffer strategy) ──────────────────────── */
+1
View File
@@ -1194,6 +1194,7 @@ ScrArr *scr_regex_match(ScrStr *s, ScrRegex *re);
double scr_regex_search(ScrStr *s, ScrRegex *re);
ScrStr *scr_regex_source(ScrRegex *re); /* +1 */
ScrStr *scr_regex_flags(ScrRegex *re); /* +1 */
ScrStr *scr_regex_to_string(ScrRegex *re); /* +1 */
/* replace: first match without /g, every match with it. */
ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep);
/* replaceAll: throws Node's TypeError when /g is missing (may-throw). */
@@ -0,0 +1,14 @@
function named(first, second) { return first + second; }
const alias = named;
for (const key of ['name', 'length', 'missing']) {
const descriptor = Object.getOwnPropertyDescriptor(alias, key);
if (descriptor) console.log(key, descriptor.value, descriptor.writable, descriptor.enumerable, descriptor.configurable);
else console.log(key, descriptor);
}
Object.defineProperties(named, { label: { value: 'tag', writable: true, enumerable: true, configurable: true } });
const label = Object.getOwnPropertyDescriptor(alias, 'label');
console.log(label.value, label.writable, label.enumerable, label.configurable);
console.log(Object.getOwnPropertyDescriptor(alias, 'name').value);
console.log(Object.hasOwn(alias, 'name'), Object.hasOwn(alias, 'length'), Object.hasOwn(alias, 'label'), Object.hasOwn(alias, 'missing'));
const arrayLength = Object.getOwnPropertyDescriptor(JSON.parse('[1,2,3]'), 'length');
console.log(arrayLength.value, arrayLength.writable, arrayLength.enumerable, arrayLength.configurable);
+13
View File
@@ -0,0 +1,13 @@
console.log('expression ' + new RegExp('a+', 'gi'));
console.log('expression ' + new RegExp(''));
console.log('expression ' + /b?/);
console.log('expression ' + new RegExp('x/y'));
console.log('expression ' + new RegExp('[a/]'));
console.log('expression ' + new RegExp('a\nb'));
for (const pattern of ['(', 'x{1}{1,}']) {
try {
console.log('expression ' + new RegExp(pattern));
} catch (error) {
console.log(error.name);
}
}