nashorn/src/jdk/nashorn/internal/objects/NativeRegExp.java
changeset 16223 dff592a332a4
parent 16217 c5ac9be5c444
child 16226 0e4f37e6cc40
equal deleted inserted replaced
16222:3e057d4357e0 16223:dff592a332a4
    25 
    25 
    26 package jdk.nashorn.internal.objects;
    26 package jdk.nashorn.internal.objects;
    27 
    27 
    28 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
    28 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
    29 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
    29 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
    30 import static jdk.nashorn.internal.runtime.linker.Lookup.MH;
    30 
    31 
       
    32 import java.lang.invoke.MethodHandle;
       
    33 import java.lang.invoke.MethodHandles;
       
    34 import java.util.ArrayList;
    31 import java.util.ArrayList;
    35 import java.util.Arrays;
    32 import java.util.Arrays;
    36 import java.util.List;
    33 import java.util.List;
    37 import java.util.regex.Matcher;
    34 import java.util.regex.Matcher;
    38 import java.util.regex.Pattern;
    35 import java.util.regex.Pattern;
    41 import jdk.nashorn.internal.objects.annotations.Function;
    38 import jdk.nashorn.internal.objects.annotations.Function;
    42 import jdk.nashorn.internal.objects.annotations.Getter;
    39 import jdk.nashorn.internal.objects.annotations.Getter;
    43 import jdk.nashorn.internal.objects.annotations.Property;
    40 import jdk.nashorn.internal.objects.annotations.Property;
    44 import jdk.nashorn.internal.objects.annotations.ScriptClass;
    41 import jdk.nashorn.internal.objects.annotations.ScriptClass;
    45 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor;
    42 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor;
       
    43 import jdk.nashorn.internal.objects.annotations.Where;
    46 import jdk.nashorn.internal.parser.RegExp;
    44 import jdk.nashorn.internal.parser.RegExp;
    47 import jdk.nashorn.internal.runtime.BitVector;
    45 import jdk.nashorn.internal.runtime.BitVector;
    48 import jdk.nashorn.internal.runtime.JSType;
    46 import jdk.nashorn.internal.runtime.JSType;
    49 import jdk.nashorn.internal.runtime.ParserException;
    47 import jdk.nashorn.internal.runtime.ParserException;
    50 import jdk.nashorn.internal.runtime.RegExpMatch;
    48 import jdk.nashorn.internal.runtime.RegExpMatch;
    55 /**
    53 /**
    56  * ECMA 15.10 RegExp Objects.
    54  * ECMA 15.10 RegExp Objects.
    57  */
    55  */
    58 @ScriptClass("RegExp")
    56 @ScriptClass("RegExp")
    59 public final class NativeRegExp extends ScriptObject {
    57 public final class NativeRegExp extends ScriptObject {
    60     static final MethodHandle REGEXP_STATICS_HANDLER = findOwnMH("regExpStaticsHandler", Object.class, Object.class, Object.class);
       
    61 
       
    62     /** ECMA 15.10.7.5 lastIndex property */
    58     /** ECMA 15.10.7.5 lastIndex property */
    63     @Property(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
    59     @Property(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
    64     public Object lastIndex;
    60     public Object lastIndex;
    65 
    61 
    66     /** Pattern string. */
    62     /** Pattern string. */
    78     /** Java regex pattern to use for match. We compile to one of these */
    74     /** Java regex pattern to use for match. We compile to one of these */
    79     private Pattern pattern;
    75     private Pattern pattern;
    80 
    76 
    81     private BitVector groupsInNegativeLookahead;
    77     private BitVector groupsInNegativeLookahead;
    82 
    78 
    83     // RegExp constructor object. Needed to support RegExp "static" properties,
    79     // Reference to global object needed to support static RegExp properties
    84     private Object constructor;
    80     private Global globalObject;
    85 
    81 
    86     /*
    82     /*
    87     public NativeRegExp() {
    83     public NativeRegExp() {
    88         init();
    84         init();
    89     }*/
    85     }*/
   327      * ECMA 15.10.7.1 source
   323      * ECMA 15.10.7.1 source
   328      *
   324      *
   329      * @param self self reference
   325      * @param self self reference
   330      * @return the input string for the regexp
   326      * @return the input string for the regexp
   331      */
   327      */
   332     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE)
   328     @Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
   333     public static Object source(final Object self) {
   329     public static Object source(final Object self) {
   334         return checkRegExp(self).input;
   330         return checkRegExp(self).input;
   335     }
   331     }
   336 
   332 
   337     /**
   333     /**
   338      * ECMA 15.10.7.2 global
   334      * ECMA 15.10.7.2 global
   339      *
   335      *
   340      * @param self self reference
   336      * @param self self reference
   341      * @return true if this regexp is flagged global, false otherwise
   337      * @return true if this regexp is flagged global, false otherwise
   342      */
   338      */
   343     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE)
   339     @Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
   344     public static Object global(final Object self) {
   340     public static Object global(final Object self) {
   345         return checkRegExp(self).global;
   341         return checkRegExp(self).global;
   346     }
   342     }
   347 
   343 
   348     /**
   344     /**
   349      * ECMA 15.10.7.3 ignoreCase
   345      * ECMA 15.10.7.3 ignoreCase
   350      *
   346      *
   351      * @param self self reference
   347      * @param self self reference
   352      * @return true if this regexp if flagged to ignore case, false otherwise
   348      * @return true if this regexp if flagged to ignore case, false otherwise
   353      */
   349      */
   354     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE)
   350     @Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
   355     public static Object ignoreCase(final Object self) {
   351     public static Object ignoreCase(final Object self) {
   356         return checkRegExp(self).ignoreCase;
   352         return checkRegExp(self).ignoreCase;
   357     }
   353     }
   358 
   354 
   359     /**
   355     /**
   360      * ECMA 15.10.7.4 multiline
   356      * ECMA 15.10.7.4 multiline
   361      *
   357      *
   362      * @param self self reference
   358      * @param self self reference
   363      * @return true if this regexp is flagged to be multiline, false otherwise
   359      * @return true if this regexp is flagged to be multiline, false otherwise
   364      */
   360      */
   365     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE | Attribute.NOT_WRITABLE)
   361     @Getter(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
   366     public static Object multiline(final Object self) {
   362     public static Object multiline(final Object self) {
   367         return checkRegExp(self).multiline;
   363         return checkRegExp(self).multiline;
       
   364     }
       
   365 
       
   366     /**
       
   367      * Getter for non-standard RegExp.input property.
       
   368      * @param self self object
       
   369      * @return last regexp input
       
   370      */
       
   371     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "input")
       
   372     public static Object getLastInput(Object self) {
       
   373         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   374         return match == null ? "" : match.getInput();
       
   375     }
       
   376 
       
   377     /**
       
   378      * Getter for non-standard RegExp.multiline property.
       
   379      * @param self self object
       
   380      * @return last regexp input
       
   381      */
       
   382     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "multiline")
       
   383     public static Object getLastMultiline(Object self) {
       
   384         return false; // doesn't ever seem to become true and isn't documented anyhwere
       
   385     }
       
   386 
       
   387     /**
       
   388      * Getter for non-standard RegExp.lastMatch property.
       
   389      * @param self self object
       
   390      * @return last regexp input
       
   391      */
       
   392     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "lastMatch")
       
   393     public static Object getLastMatch(Object self) {
       
   394         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   395         return match == null ? "" : match.getGroup(0);
       
   396     }
       
   397 
       
   398     /**
       
   399      * Getter for non-standard RegExp.lastParen property.
       
   400      * @param self self object
       
   401      * @return last regexp input
       
   402      */
       
   403     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "lastParen")
       
   404     public static Object getLastParen(Object self) {
       
   405         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   406         return match == null ? "" : match.getLastParen();
       
   407     }
       
   408 
       
   409     /**
       
   410      * Getter for non-standard RegExp.leftContext property.
       
   411      * @param self self object
       
   412      * @return last regexp input
       
   413      */
       
   414     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "leftContext")
       
   415     public static Object getLeftContext(Object self) {
       
   416         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   417         return match == null ? "" : match.getInput().substring(0, match.getIndex());
       
   418     }
       
   419 
       
   420     /**
       
   421      * Getter for non-standard RegExp.rightContext property.
       
   422      * @param self self object
       
   423      * @return last regexp input
       
   424      */
       
   425     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "rightContext")
       
   426     public static Object getRightContext(Object self) {
       
   427         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   428         return match == null ? "" : match.getInput().substring(match.getIndex() + match.length());
       
   429     }
       
   430 
       
   431     /**
       
   432      * Getter for non-standard RegExp.$1 property.
       
   433      * @param self self object
       
   434      * @return last regexp input
       
   435      */
       
   436     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$1")
       
   437     public static Object getGroup1(Object self) {
       
   438         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   439         return match == null ? "" : match.getGroup(1);
       
   440     }
       
   441 
       
   442     /**
       
   443      * Getter for non-standard RegExp.$2 property.
       
   444      * @param self self object
       
   445      * @return last regexp input
       
   446      */
       
   447     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$2")
       
   448     public static Object getGroup2(Object self) {
       
   449         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   450         return match == null ? "" : match.getGroup(2);
       
   451     }
       
   452 
       
   453     /**
       
   454      * Getter for non-standard RegExp.$3 property.
       
   455      * @param self self object
       
   456      * @return last regexp input
       
   457      */
       
   458     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$3")
       
   459     public static Object getGroup3(Object self) {
       
   460         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   461         return match == null ? "" : match.getGroup(3);
       
   462     }
       
   463 
       
   464     /**
       
   465      * Getter for non-standard RegExp.$4 property.
       
   466      * @param self self object
       
   467      * @return last regexp input
       
   468      */
       
   469     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$4")
       
   470     public static Object getGroup4(Object self) {
       
   471         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   472         return match == null ? "" : match.getGroup(4);
       
   473     }
       
   474 
       
   475     /**
       
   476      * Getter for non-standard RegExp.$5 property.
       
   477      * @param self self object
       
   478      * @return last regexp input
       
   479      */
       
   480     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$5")
       
   481     public static Object getGroup5(Object self) {
       
   482         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   483         return match == null ? "" : match.getGroup(5);
       
   484     }
       
   485 
       
   486     /**
       
   487      * Getter for non-standard RegExp.$6 property.
       
   488      * @param self self object
       
   489      * @return last regexp input
       
   490      */
       
   491     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$6")
       
   492     public static Object getGroup6(Object self) {
       
   493         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   494         return match == null ? "" : match.getGroup(6);
       
   495     }
       
   496 
       
   497     /**
       
   498      * Getter for non-standard RegExp.$7 property.
       
   499      * @param self self object
       
   500      * @return last regexp input
       
   501      */
       
   502     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$7")
       
   503     public static Object getGroup7(Object self) {
       
   504         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   505         return match == null ? "" : match.getGroup(7);
       
   506     }
       
   507 
       
   508     /**
       
   509      * Getter for non-standard RegExp.$8 property.
       
   510      * @param self self object
       
   511      * @return last regexp input
       
   512      */
       
   513     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$8")
       
   514     public static Object getGroup8(Object self) {
       
   515         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   516         return match == null ? "" : match.getGroup(8);
       
   517     }
       
   518 
       
   519     /**
       
   520      * Getter for non-standard RegExp.$9 property.
       
   521      * @param self self object
       
   522      * @return last regexp input
       
   523      */
       
   524     @Getter(where = Where.CONSTRUCTOR, attributes = Attribute.CONSTANT, name = "$9")
       
   525     public static Object getGroup9(Object self) {
       
   526         final RegExpMatch match = Global.instance().getLastRegExpMatch();
       
   527         return match == null ? "" : match.getGroup(9);
   368     }
   528     }
   369 
   529 
   370     private RegExpMatch execInner(final String string) {
   530     private RegExpMatch execInner(final String string) {
   371         if (this.pattern == null) {
   531         if (this.pattern == null) {
   372             return null; // never matches or similar, e.g. a[]
   532             return null; // never matches or similar, e.g. a[]
   387 
   547 
   388         if (global) {
   548         if (global) {
   389             setLastIndex(matcher.end());
   549             setLastIndex(matcher.end());
   390         }
   550         }
   391 
   551 
   392         return new RegExpMatch(string, matcher.start(), groups(matcher));
   552         final RegExpMatch match = new RegExpMatch(string, matcher.start(), groups(matcher));
       
   553         globalObject.setLastRegExpMatch(match);
       
   554         return match;
   393     }
   555     }
   394 
   556 
   395     /**
   557     /**
   396      * Convert java.util.regex.Matcher groups to JavaScript groups.
   558      * Convert java.util.regex.Matcher groups to JavaScript groups.
   397      * That is, replace null and groups that didn't match with undefined.
   559      * That is, replace null and groups that didn't match with undefined.
   423      *
   585      *
   424      * @param string String to match.
   586      * @param string String to match.
   425      * @return NativeArray of matches, string or null.
   587      * @return NativeArray of matches, string or null.
   426      */
   588      */
   427     public Object exec(final String string) {
   589     public Object exec(final String string) {
   428         final RegExpMatch m = execInner(string);
   590         final RegExpMatch match = execInner(string);
   429         // the input string
   591 
   430         if (m == null) {
   592         if (match == null) {
   431             return setLastRegExpMatch(null);
   593             return null;
   432         }
   594         }
   433 
   595 
   434         return setLastRegExpMatch(new NativeRegExpExecResult(m));
   596         return new NativeRegExpExecResult(match);
   435     }
       
   436 
       
   437     // Name of the "last successful match" property of the RegExp constructor
       
   438     static final String LAST_REGEXP_MATCH = "__last_regexp_match__";
       
   439 
       
   440     /**
       
   441      * Handles "static" properties of RegExp constructor. These are "deprecated"
       
   442      * properties of RegExp constructor.
       
   443      *
       
   444      * @param self self object passed to this method
       
   445      * @param name name of the property being searched
       
   446      *
       
   447      * @return value of the specified property or undefined if not found
       
   448      */
       
   449     public static Object regExpStaticsHandler(final Object self, final  Object name) {
       
   450         final String propName = JSType.toString(name);
       
   451         if (self instanceof ScriptObject) {
       
   452             final ScriptObject sobj = (ScriptObject)self;
       
   453             final Object value = sobj.get(LAST_REGEXP_MATCH);
       
   454             if (! (value instanceof NativeRegExpExecResult)) {
       
   455                 return UNDEFINED;
       
   456             }
       
   457 
       
   458             // get the last match object
       
   459             final NativeRegExpExecResult lastMatch = (NativeRegExpExecResult)value;
       
   460 
       
   461             // look for $1... $9
       
   462             if (propName.length() > 0 && propName.charAt(0) == '$') {
       
   463                 int index = 0;
       
   464                 try {
       
   465                     index = Integer.parseInt(propName.substring(1));
       
   466                 } catch (final Exception ignored) {
       
   467                     return UNDEFINED;
       
   468                 }
       
   469 
       
   470                 // index out of range
       
   471                 if (index < 1 && index > 9) {
       
   472                     return UNDEFINED;
       
   473                 }
       
   474 
       
   475                 // retrieve indexed value from last match object.
       
   476                 return lastMatch.get(index);
       
   477             }
       
   478 
       
   479             // misc. "static" properties supported
       
   480             switch (propName) {
       
   481                 case "input": {
       
   482                     return lastMatch.input;
       
   483                 }
       
   484 
       
   485                 case "lastMatch": {
       
   486                     return lastMatch.get(0);
       
   487                 }
       
   488 
       
   489                 case "lastParen": {
       
   490                     final int len = ((Number)NativeRegExpExecResult.length(lastMatch)).intValue();
       
   491                     return (len > 0)? lastMatch.get(len - 1) : UNDEFINED;
       
   492                 }
       
   493             }
       
   494         }
       
   495 
       
   496         return UNDEFINED;
       
   497     }
       
   498 
       
   499     // Support for RegExp static properties. We set last successful match
       
   500     // to the RegExp constructor object.
       
   501     private Object setLastRegExpMatch(final Object match) {
       
   502         if (constructor instanceof ScriptObject) {
       
   503             ((ScriptObject)constructor).set(LAST_REGEXP_MATCH, match, isStrictContext());
       
   504         }
       
   505         return match;
       
   506     }
   597     }
   507 
   598 
   508     /**
   599     /**
   509      * Executes a search for a match within a string based on a regular
   600      * Executes a search for a match within a string based on a regular
   510      * expression.
   601      * expression.
   742      *
   833      *
   743      * @param string String to match.
   834      * @param string String to match.
   744      * @return Index of match.
   835      * @return Index of match.
   745      */
   836      */
   746     Object search(final String string) {
   837     Object search(final String string) {
   747         final RegExpMatch m = execInner(string);
   838         final RegExpMatch match = execInner(string);
   748         // the input string
   839 
   749         if (m == null) {
   840         if (match == null) {
   750             setLastRegExpMatch(null);
       
   751             return -1;
   841             return -1;
   752         }
   842         }
   753 
   843 
   754         setLastRegExpMatch(new NativeRegExpExecResult(m));
   844         return match.getIndex();
   755         return m.getIndex();
       
   756     }
   845     }
   757 
   846 
   758     /**
   847     /**
   759      * Fast lastIndex getter
   848      * Fast lastIndex getter
   760      * @return last index property as int
   849      * @return last index property as int
   778     public void setLastIndex(final int lastIndex) {
   867     public void setLastIndex(final int lastIndex) {
   779         this.lastIndex = JSType.toObject(lastIndex);
   868         this.lastIndex = JSType.toObject(lastIndex);
   780     }
   869     }
   781 
   870 
   782     private void init() {
   871     private void init() {
   783         final ScriptObject proto = Global.instance().getRegExpPrototype();
   872         // Keep reference to global object to support "static" properties of RegExp
   784         this.setProto(proto);
   873         this.globalObject = Global.instance();
   785         // retrieve constructor to support "static" properties of RegExp
   874         this.setProto(globalObject.getRegExpPrototype());
   786         this.constructor = PrototypeObject.getConstructor(proto);
       
   787     }
   875     }
   788 
   876 
   789     private static NativeRegExp checkRegExp(final Object self) {
   877     private static NativeRegExp checkRegExp(final Object self) {
   790         Global.checkObjectCoercible(self);
   878         Global.checkObjectCoercible(self);
   791         if (self instanceof NativeRegExp) {
   879         if (self instanceof NativeRegExp) {
   844 
   932 
   845     private void setGroupsInNegativeLookahead(final BitVector groupsInNegativeLookahead) {
   933     private void setGroupsInNegativeLookahead(final BitVector groupsInNegativeLookahead) {
   846         this.groupsInNegativeLookahead = groupsInNegativeLookahead;
   934         this.groupsInNegativeLookahead = groupsInNegativeLookahead;
   847     }
   935     }
   848 
   936 
   849     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
       
   850         return MH.findStatic(MethodHandles.publicLookup(), NativeRegExp.class, name, MH.type(rtype, types));
       
   851     }
       
   852 }
   937 }