1 /*
2  * Copyright (c) 2010, 2013, 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         }
86     }
87 
88     @Override
match(final String input)89     public RegExpMatcher match(final String input) {
90         if (regex == null) {
91             return null;
92         }
93 
94         return new JoniMatcher(input);
95     }
96 
97     /**
98      * RegExp Factory class for Joni regexp engine.
99      */
100     public static class Factory extends RegExpFactory {
101 
102         @Override
compile(final String pattern, final String flags)103         public RegExp compile(final String pattern, final String flags) throws ParserException {
104             return new JoniRegExp(pattern, flags);
105         }
106 
107     }
108 
109     class JoniMatcher implements RegExpMatcher {
110         final String input;
111         final Matcher joniMatcher;
112 
JoniMatcher(final String input)113         JoniMatcher(final String input) {
114             this.input = input;
115             this.joniMatcher = regex.matcher(input.toCharArray());
116         }
117 
118         @Override
search(final int start)119         public boolean search(final int start) {
120             return joniMatcher.search(start, input.length(), Option.NONE) > -1;
121         }
122 
123         @Override
getInput()124         public String getInput() {
125             return input;
126         }
127 
128         @Override
start()129         public int start() {
130             return joniMatcher.getBegin();
131         }
132 
133         @Override
start(final int group)134         public int start(final int group) {
135             return group == 0 ? start() : joniMatcher.getRegion().beg[group];
136         }
137 
138         @Override
end()139         public int end() {
140             return joniMatcher.getEnd();
141         }
142 
143         @Override
end(final int group)144         public int end(final int group) {
145             return group == 0 ? end() : joniMatcher.getRegion().end[group];
146         }
147 
148         @Override
group()149         public String group() {
150             return input.substring(joniMatcher.getBegin(), joniMatcher.getEnd());
151         }
152 
153         @Override
group(final int group)154         public String group(final int group) {
155             if (group == 0) {
156                 return group();
157             }
158             final Region region = joniMatcher.getRegion();
159             return input.substring(region.beg[group], region.end[group]);
160         }
161 
162         @Override
groupCount()163         public int groupCount() {
164             final Region region = joniMatcher.getRegion();
165             return region == null ? 0 : region.numRegs - 1;
166         }
167     }
168 }
169