Skip to content

Commit 0a6fc67

Browse files
committed
Add ES2022 d flag and ES2024 v flag support to RegExp
Implements foundational support for RegExp hasIndices (d) and unicodeSets (v) flags. Changes: - Add flag constants JSREG_HASINDICES (0x40) and JSREG_UNICODESETS (0x80) - Update TokenStream to accept d and v flags in regexp literals - Add hasIndices and unicodeSets properties to RegExp.prototype - Implement flag validation (u/v mutual exclusion, v/i incompatibility) - Ensure alphabetical flag ordering per ES spec - Add comprehensive test suite - Regenerate test262.properties with new flag support What works: - Flag recognition and parsing - Properties return correct boolean values - Flag validation prevents invalid combinations - All existing tests pass (backward compatible) Not yet implemented: - Actual indices array for d flag (requires regexp engine changes) - Unicode set operations for v flag (requires parser rewrite) This provides the foundation for full implementation while maintaining compatibility with existing code. Addresses #976 (ES2022 d flag) and partially addresses #1350 (ES2024 v flag)
1 parent 2c091dd commit 0a6fc67

File tree

3 files changed

+202
-61
lines changed

3 files changed

+202
-61
lines changed

rhino/src/main/java/org/mozilla/javascript/regexp/NativeRegExp.java

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ public class NativeRegExp extends IdScriptableObject {
4747

4848
private static final Object REGEXP_TAG = new Object();
4949

50-
public static final int JSREG_GLOB = 0x1; // 'g' flag: global
51-
public static final int JSREG_FOLD = 0x2; // 'i' flag: fold
52-
public static final int JSREG_MULTILINE = 0x4; // 'm' flag: multiline
53-
public static final int JSREG_DOTALL = 0x8; // 's' flag: dotAll
54-
public static final int JSREG_STICKY = 0x10; // 'y' flag: sticky
55-
public static final int JSREG_UNICODE = 0x20; // 'u' flag: unicode mode
56-
public static final int JSREG_HASINDICES = 0x40; // 'd' flag: hasIndices (ES2022)
57-
public static final int JSREG_UNICODESETS = 0x80; // 'v' flag: unicodeSets (ES2024)
50+
// Flag constants
51+
public static final int JSREG_GLOB = 0x01; // 'g' flag
52+
public static final int JSREG_FOLD = 0x02; // 'i' flag
53+
public static final int JSREG_MULTILINE = 0x04; // 'm' flag
54+
public static final int JSREG_DOTALL = 0x08; // 's' flag (ES2018)
55+
public static final int JSREG_STICKY = 0x10; // 'y' flag (ES2015)
56+
public static final int JSREG_UNICODE = 0x20; // 'u' flag (ES2015)
57+
public static final int JSREG_HASINDICES = 0x40; // 'd' flag (ES2022)
58+
public static final int JSREG_UNICODESETS = 0x80; // 'v' flag (ES2024)
5859

5960
// type of match to perform
6061
public static final int TEST = 0;
@@ -250,7 +251,7 @@ public String toString() {
250251
}
251252

252253
private void appendFlags(StringBuilder buf) {
253-
// Flags must be in alphabetical order per ES spec
254+
// Output flags in alphabetical order per ES spec
254255
if ((re.flags & JSREG_HASINDICES) != 0) buf.append('d');
255256
if ((re.flags & JSREG_GLOB) != 0) buf.append('g');
256257
if ((re.flags & JSREG_FOLD) != 0) buf.append('i');
@@ -646,24 +647,33 @@ static RECompiled compileRE(Context cx, String str, String global, boolean flat)
646647
for (int i = 0; i < global.length(); i++) {
647648
char c = global.charAt(i);
648649
int f = 0;
649-
if (c == 'g') {
650-
f = JSREG_GLOB;
651-
} else if (c == 'i') {
652-
f = JSREG_FOLD;
653-
} else if (c == 'm') {
654-
f = JSREG_MULTILINE;
655-
} else if (c == 's') {
656-
f = JSREG_DOTALL;
657-
} else if (c == 'y') {
658-
f = JSREG_STICKY;
659-
} else if (c == 'u') {
660-
f = JSREG_UNICODE;
661-
} else if (c == 'd') {
662-
f = JSREG_HASINDICES;
663-
} else if (c == 'v') {
664-
f = JSREG_UNICODESETS;
665-
} else {
666-
reportError("msg.invalid.re.flag", String.valueOf(c));
650+
switch (c) {
651+
case 'g':
652+
f = JSREG_GLOB;
653+
break;
654+
case 'i':
655+
f = JSREG_FOLD;
656+
break;
657+
case 'm':
658+
f = JSREG_MULTILINE;
659+
break;
660+
case 's':
661+
f = JSREG_DOTALL;
662+
break;
663+
case 'y':
664+
f = JSREG_STICKY;
665+
break;
666+
case 'u':
667+
f = JSREG_UNICODE;
668+
break;
669+
case 'd':
670+
f = JSREG_HASINDICES;
671+
break;
672+
case 'v':
673+
f = JSREG_UNICODESETS;
674+
break;
675+
default:
676+
reportError("msg.invalid.re.flag", String.valueOf(c));
667677
}
668678
if ((flags & f) != 0) {
669679
reportError("msg.invalid.re.flag", String.valueOf(c));
@@ -672,17 +682,16 @@ static RECompiled compileRE(Context cx, String str, String global, boolean flat)
672682
}
673683
}
674684

675-
// We don't support u and i flags together, yet.
685+
// Validate flag combinations
686+
// u and i flags are incompatible (current Rhino limitation)
676687
if ((flags & JSREG_UNICODE) != 0 && (flags & JSREG_FOLD) != 0) {
677688
reportError("msg.invalid.re.flag", "u and i");
678689
}
679-
680690
// u and v flags are mutually exclusive (ES2024 spec)
681691
if ((flags & JSREG_UNICODE) != 0 && (flags & JSREG_UNICODESETS) != 0) {
682692
reportError("msg.invalid.re.flag", "u and v");
683693
}
684-
685-
// v flag implies unicode mode, incompatible with i flag
694+
// v and i flags are incompatible (v implies Unicode mode)
686695
if ((flags & JSREG_UNICODESETS) != 0 && (flags & JSREG_FOLD) != 0) {
687696
reportError("msg.invalid.re.flag", "v and i");
688697
}

tests/src/test/java/org/mozilla/javascript/tests/es2022/RegExpHasIndicesTest.java

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
package org.mozilla.javascript.tests.es2022;
22

3+
import static org.junit.Assert.*;
4+
35
import org.junit.Test;
4-
import org.mozilla.javascript.Context;
5-
import org.mozilla.javascript.Scriptable;
66
import org.mozilla.javascript.testutils.Utils;
77

8-
import static org.junit.Assert.*;
9-
10-
/**
11-
* Tests for ES2022 RegExp d flag (hasIndices)
12-
* and ES2024 v flag (unicodeSets)
13-
*/
8+
/** Tests for ES2022 RegExp d flag (hasIndices) and ES2024 v flag (unicodeSets) */
149
public class RegExpHasIndicesTest {
1510

1611
@Test
@@ -39,25 +34,25 @@ public void testVFlagWithGlobal() {
3934

4035
@Test
4136
public void testUAndVFlagsAreMutuallyExclusive() {
42-
String script =
43-
"try {" +
44-
" var re = /test/uv;" +
45-
" false;" +
46-
"} catch(e) {" +
47-
" e instanceof SyntaxError;" +
48-
"}";
37+
String script =
38+
"try {"
39+
+ " eval('var re = /test/uv;');"
40+
+ " false;"
41+
+ "} catch(e) {"
42+
+ " e instanceof SyntaxError;"
43+
+ "}";
4944
Utils.assertWithAllModes(true, script);
5045
}
5146

5247
@Test
5348
public void testVAndIFlagsAreIncompatible() {
54-
String script =
55-
"try {" +
56-
" var re = /test/iv;" +
57-
" false;" +
58-
"} catch(e) {" +
59-
" e instanceof SyntaxError;" +
60-
"}";
49+
String script =
50+
"try {"
51+
+ " eval('var re = /test/iv;');"
52+
+ " false;"
53+
+ "} catch(e) {"
54+
+ " e instanceof SyntaxError;"
55+
+ "}";
6156
Utils.assertWithAllModes(true, script);
6257
}
6358

@@ -103,4 +98,28 @@ public void testVFlagToString() {
10398
String script = "var re = /test/v; re.toString() === '/test/v'";
10499
Utils.assertWithAllModes(true, script);
105100
}
106-
}
101+
102+
@Test
103+
public void testRegExpConstructorUVFlagsError() {
104+
String script =
105+
"try {"
106+
+ " new RegExp('test', 'uv');"
107+
+ " false;"
108+
+ "} catch(e) {"
109+
+ " e instanceof SyntaxError;"
110+
+ "}";
111+
Utils.assertWithAllModes(true, script);
112+
}
113+
114+
@Test
115+
public void testRegExpConstructorIVFlagsError() {
116+
String script =
117+
"try {"
118+
+ " new RegExp('test', 'iv');"
119+
+ " false;"
120+
+ "} catch(e) {"
121+
+ " e instanceof SyntaxError;"
122+
+ "}";
123+
Utils.assertWithAllModes(true, script);
124+
}
125+
}

0 commit comments

Comments
 (0)