Skip to content

runtime: implement RegExp sticky, dotAll, unicode, and hasIndices flags #2828

Closed

Description

@andrewtdiz

Summary

Perry's RegExp runtime records and implements only g, i, and m in the object header. Node exposes additional observable flags and matching semantics for y (sticky), s (dotAll), u (unicode), and d (hasIndices). Perry currently reports these flag properties as false and does not implement sticky matching or match indices.

Expected Node behavior

Local Node v25.9.0 probe:

const sticky = /a/y;
console.log(sticky.flags, sticky.sticky, sticky.global, sticky.lastIndex); // "y", true, false, 0
console.log(sticky.exec("ba"), sticky.lastIndex); // null, 0
sticky.lastIndex = 1;
console.log(sticky.exec("ba").index, sticky.lastIndex); // 1, 2

const dotAll = /a.b/s;
console.log(dotAll.flags, dotAll.dotAll, dotAll.test("a\nb")); // "s", true, true

const unicode = /./u;
console.log(unicode.flags, unicode.unicode); // "u", true

const indexed = /(a)/d;
const match = indexed.exec("a");
console.log(indexed.flags, indexed.hasIndices, match.indices[0], match.indices[1]);
// "d", true, [0, 1], [0, 1]

Current Perry implementation

Source evidence from current tree:

  • crates/perry-runtime/src/regex.rs::RegExpHeader stores case_insensitive, global, multiline, and last_index, but has no sticky/dotAll/unicode/hasIndices booleans.
  • js_regexp_new() derives only flags_str.contains('i'), contains('g'), and contains('m'); other flags remain only in the saved flags string.
  • get_or_compile_regex() only injects Rust regex options for case-insensitive and multiline modes. There is no dotAll translation (s) such as (?s) / equivalent, so . will not match newlines for /s patterns.
  • js_regexp_exec() treats only global as stateful: it starts at last_index only when global is true and resets/updates last_index only on that path. Sticky regexes should also be stateful and must match exactly at lastIndex instead of searching later in the string.
  • crates/perry-runtime/src/object/field_get_set.rs routes RegExp property reads for .source, .flags, .lastIndex, .global, .ignoreCase, and .multiline, but returns false for .sticky, .unicode, .dotAll, and .hasIndices unconditionally.
  • There is no visible support for the d flag's match.indices array in the js_regexp_exec() result path.

Suggested test surface

Add parity tests for the properties and stateful matching:

const sticky = /a/y;
assert.equal(sticky.flags, "y");
assert.equal(sticky.sticky, true);
assert.equal(sticky.global, false);
assert.equal(sticky.exec("ba"), null);
sticky.lastIndex = 1;
const sm = sticky.exec("ba")!;
assert.equal(sm.index, 1);
assert.equal(sticky.lastIndex, 2);

const dotAll = /a.b/s;
assert.equal(dotAll.dotAll, true);
assert.equal(dotAll.test("a\nb"), true);

const unicode = /./u;
assert.equal(unicode.unicode, true);

const indexed = /(a)/d;
const im = indexed.exec("a")! as any;
assert.equal(indexed.hasIndices, true);
assert.deepEqual(im.indices[0], [0, 1]);
assert.deepEqual(im.indices[1], [0, 1]);

Scope / non-goals

This is separate from earlier RegExp allocation/router fixes such as #637. It is specifically about RegExp flag property values and the matching/result semantics those flags enable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions