1 /*
2  * Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.nashorn.internal.runtime.regexp;
27 
28 import java.util.regex.Pattern;
29 import java.util.regex.PatternSyntaxException;
30 import jdk.nashorn.internal.runtime.ParserException;
31 import jdk.nashorn.internal.runtime.regexp.joni.Matcher;
32 import jdk.nashorn.internal.runtime.regexp.joni.Option;
33 import jdk.nashorn.internal.runtime.regexp.joni.Regex;
34 import jdk.nashorn.internal.runtime.regexp.joni.Region;
35 import jdk.nashorn.internal.runtime.regexp.joni.Syntax;
36 import jdk.nashorn.internal.runtime.regexp.joni.exception.JOniException;
37 
38 /**
39  * Regular expression implementation based on the Joni engine from the JRuby project.
40  */
41 public class JoniRegExp extends RegExp {
42 
43     /** Compiled Joni Regex */
44     private Regex regex;
45 
46     /**
47      * Construct a Regular expression from the given {@code pattern} and {@code flags} strings.
48      *
49      * @param pattern RegExp pattern string
50      * @param flags RegExp flag string
51      * @throws ParserException if flags is invalid or pattern string has syntax error.
52      */
JoniRegExp(final String pattern, final String flags)53     public JoniRegExp(final String pattern, final String flags) throws ParserException {
54         super(pattern, flags);
55 
56         int option = Option.SINGLELINE;
57 
58         if (this.isIgnoreCase()) {
59             option |= Option.IGNORECASE;
60         }
61         if (this.isMultiline()) {
62             option &= ~Option.SINGLELINE;
63             option |= Option.NEGATE_SINGLELINE;
64         }
65 
66         try {
67             RegExpScanner parsed;
68 
69             try {
70                 parsed = RegExpScanner.scan(pattern);
71             } catch (final PatternSyntaxException e) {
72                 // refine the exception with a better syntax error, if this
73                 // passes, just rethrow what we have
74                 Pattern.compile(pattern, 0);
75                 throw e;
76             }
77 
78             if (parsed != null) {
79                 final char[] javaPattern = parsed.getJavaPattern().toCharArray();
80                 this.regex = new Regex(javaPattern, 0, javaPattern.length, option, Syntax.JAVASCRIPT);
81                 this.groupsInNegativeLookahead = parsed.getGroupsInNegativeLookahead();
82             }
83         } catch (final PatternSyntaxException | JOniException e2) {
84             throwParserException("syntax", e2.getMessage());
85         } catch (StackOverflowError e3) {
86             throw new RuntimeException(e3);
87         }
88     }
89 
90     @Override
match(final String input)91     public RegExpMatcher match(final String input) {
92         if (regex == null) {
93             return null;
94         }
95 
96         return new JoniMatcher(input);
97     }
98 
99     /**
100      * RegExp Factory class for Joni regexp engine.
101      */
102     public static class Factory extends RegExpFactory {
103 
104         @Override
compile(final String pattern, final String flags)105         public RegExp compile(final String pattern, final String flags) throws ParserException {
106             return new JoniRegExp(pattern, flags);
107         }
108 
109     }
110 
111     class JoniMatcher implements RegExpMatcher {
112         final String input;
113         final Matcher joniMatcher;
114 
JoniMatcher(final String input)115         JoniMatcher(final String input) {
116             this.input = input;
117             this.joniMatcher = regex.matcher(input.toCharArray());
118         }
119 
120         @Override
search(final int start)121         public boolean search(final int start) {
122             return joniMatcher.search(start, input.length(), Option.NONE) > -1;
123         }
124 
125         @Override
getInput()126         public String getInput() {
127             return input;
128         }
129 
130         @Override
start()131         public int start() {
132             return joniMatcher.getBegin();
133         }
134 
135         @Override
start(final int group)136         public int start(final int group) {
137             return group == 0 ? start() : joniMatcher.getRegion().beg[group];
138         }
139 
140         @Override
end()141         public int end() {
142             return joniMatcher.getEnd();
143         }
144 
145         @Override
end(final int group)146         public int end(final int group) {
147             return group == 0 ? end() : joniMatcher.getRegion().end[group];
148         }
149 
150         @Override
group()151         public String group() {
152             return input.substring(joniMatcher.getBegin(), joniMatcher.getEnd());
153         }
154 
155         @Override
group(final int group)156         public String group(final int group) {
157             if (group == 0) {
158                 return group();
159             }
160             final Region region = joniMatcher.getRegion();
161             return input.substring(region.beg[group], region.end[group]);
162         }
163 
164         @Override
groupCount()165         public int groupCount() {
166             final Region region = joniMatcher.getRegion();
167             return region == null ? 0 : region.numRegs - 1;
168         }
169     }
170 }
171