001 /*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.jet.lang.resolve.constants;
018
019 import com.google.common.base.Function;
020 import org.jetbrains.annotations.NotNull;
021 import org.jetbrains.annotations.Nullable;
022 import org.jetbrains.jet.lang.psi.JetEscapeStringTemplateEntry;
023 import org.jetbrains.jet.lang.psi.JetLiteralStringTemplateEntry;
024 import org.jetbrains.jet.lang.psi.JetStringTemplateEntry;
025 import org.jetbrains.jet.lang.psi.JetVisitorVoid;
026 import org.jetbrains.jet.lang.types.*;
027 import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
028 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
029
030 import java.util.List;
031
032 public class CompileTimeConstantResolver {
033 public static final ErrorValue OUT_OF_RANGE = new ErrorValue("The value is out of range");
034
035 private final KotlinBuiltIns builtIns;
036
037 public CompileTimeConstantResolver() {
038 this.builtIns = KotlinBuiltIns.getInstance();
039 }
040
041 @NotNull
042 public CompileTimeConstant<?> getIntegerValue(@NotNull String text, @NotNull JetType expectedType) {
043 if (noExpectedType(expectedType)) {
044 Long value = parseLongValue(text);
045 if (value == null) {
046 return OUT_OF_RANGE;
047 }
048
049 if (Integer.MIN_VALUE <= value && value <= Integer.MAX_VALUE) {
050 return new IntValue(value.intValue());
051 }
052 return new LongValue(value);
053 }
054 Function<Long, ? extends CompileTimeConstant<?>> create;
055 long lowerBound;
056 long upperBound;
057 TypeConstructor constructor = expectedType.getConstructor();
058 if (constructor == builtIns.getInt().getTypeConstructor()) {
059 create = IntValue.CREATE;
060 lowerBound = Integer.MIN_VALUE;
061 upperBound = Integer.MAX_VALUE;
062 }
063 else if (constructor == builtIns.getLong().getTypeConstructor()) {
064 create = LongValue.CREATE;
065 lowerBound = Long.MIN_VALUE;
066 upperBound = Long.MAX_VALUE;
067 }
068 else if (constructor == builtIns.getShort().getTypeConstructor()) {
069 create = ShortValue.CREATE;
070 lowerBound = Short.MIN_VALUE;
071 upperBound = Short.MAX_VALUE;
072 }
073 else if (constructor == builtIns.getByte().getTypeConstructor()) {
074 create = ByteValue.CREATE;
075 lowerBound = Byte.MIN_VALUE;
076 upperBound = Byte.MAX_VALUE;
077 }
078 else {
079 JetTypeChecker typeChecker = JetTypeChecker.INSTANCE;
080 JetType intType = builtIns.getIntType();
081 JetType longType = builtIns.getLongType();
082 if (typeChecker.isSubtypeOf(intType, expectedType)) {
083 return getIntegerValue(text, intType);
084 }
085 else if (typeChecker.isSubtypeOf(longType, expectedType)) {
086 return getIntegerValue(text, longType);
087 }
088 else {
089 return new ErrorValue("An integer literal does not conform to the expected type " + expectedType);
090 }
091 }
092 Long value = parseLongValue(text);
093
094 if (value != null && lowerBound <= value && value <= upperBound) {
095 return create.apply(value);
096 }
097 return new ErrorValue("An integer literal does not conform to the expected type " + expectedType);
098 }
099
100 @Nullable
101 private static Long parseLongValue(String text) {
102 try {
103 long value;
104 if (text.startsWith("0x") || text.startsWith("0X")) {
105 String hexString = text.substring(2);
106 value = Long.parseLong(hexString, 16);
107 }
108 else if (text.startsWith("0b") || text.startsWith("0B")) {
109 String binString = text.substring(2);
110 value = Long.parseLong(binString, 2);
111 }
112 else {
113 value = Long.parseLong(text);
114 }
115 return value;
116 }
117 catch (NumberFormatException e) {
118 return null;
119 }
120 }
121
122 @NotNull
123 public CompileTimeConstant<?> getFloatValue(@NotNull String text, @NotNull JetType expectedType) {
124 if (noExpectedType(expectedType)
125 || JetTypeChecker.INSTANCE.isSubtypeOf(builtIns.getDoubleType(), expectedType)) {
126 try {
127 return new DoubleValue(Double.parseDouble(text));
128 }
129 catch (NumberFormatException e) {
130 return OUT_OF_RANGE;
131 }
132 }
133 else if (JetTypeChecker.INSTANCE.isSubtypeOf(builtIns.getFloatType(), expectedType)) {
134 try {
135 return new FloatValue(Float.parseFloat(text));
136 }
137 catch (NumberFormatException e) {
138 return OUT_OF_RANGE;
139 }
140 }
141 else {
142 return new ErrorValue("A floating-point literal does not conform to the expected type " + expectedType);
143 }
144 }
145
146 @Nullable
147 private CompileTimeConstant<?> checkNativeType(String text, JetType expectedType, String title, JetType nativeType) {
148 if (!noExpectedType(expectedType)
149 && !JetTypeChecker.INSTANCE.isSubtypeOf(nativeType, expectedType)) {
150 return new ErrorValue("A " + title + " literal " + text + " does not conform to the expected type " + expectedType);
151 }
152 return null;
153 }
154
155 @NotNull
156 public CompileTimeConstant<?> getBooleanValue(@NotNull String text, @NotNull JetType expectedType) {
157 CompileTimeConstant<?> error = checkNativeType(text, expectedType, "boolean", builtIns.getBooleanType());
158 if (error != null) {
159 return error;
160 }
161 if ("true".equals(text)) {
162 return BooleanValue.TRUE;
163 }
164 else if ("false".equals(text)) {
165 return BooleanValue.FALSE;
166 }
167 throw new IllegalStateException("Must not happen. A boolean literal has text: " + text);
168 }
169
170 @NotNull
171 public CompileTimeConstant<?> getCharValue(@NotNull String text, @NotNull JetType expectedType) {
172 CompileTimeConstant<?> error = checkNativeType(text, expectedType, "character", builtIns.getCharType());
173 if (error != null) {
174 return error;
175 }
176
177 // Strip the quotes
178 if (text.length() < 2 || text.charAt(0) != '\'' || text.charAt(text.length() - 1) != '\'') {
179 return new ErrorValue("Incorrect character literal");
180 }
181 text = text.substring(1, text.length() - 1); // now there're no quotes
182
183 if (text.length() == 0) {
184 return new ErrorValue("Empty character literal");
185 }
186
187 if (text.charAt(0) != '\\') {
188 // No escape
189 if (text.length() == 1) {
190 return new CharValue(text.charAt(0));
191 }
192 return new ErrorValue("Too many characters in a character literal '" + text + "'");
193 }
194 return escapedStringToCharValue(text);
195 }
196
197 @NotNull
198 public static CompileTimeConstant<?> escapedStringToCharValue(@NotNull String text) {
199 assert text.length() > 0 && text.charAt(0) == '\\' : "Only escaped sequences must be passed to this routine: " + text;
200
201 // Escape
202 String escape = text.substring(1); // strip the slash
203 switch (escape.length()) {
204 case 0:
205 // bare slash
206 return illegalEscape(text);
207 case 1:
208 // one-char escape
209 Character escaped = translateEscape(escape.charAt(0));
210 if (escaped == null) {
211 return illegalEscape(text);
212 }
213 return new CharValue(escaped);
214 case 5:
215 // unicode escape
216 if (escape.charAt(0) == 'u') {
217 try {
218 Integer intValue = Integer.valueOf(escape.substring(1), 16);
219 return new CharValue((char) intValue.intValue());
220 } catch (NumberFormatException e) {
221 // Will be reported below
222 }
223 }
224 break;
225 }
226 return illegalEscape(text);
227 }
228
229 private static ErrorValue illegalEscape(String text) {
230 return new ErrorValue("Illegal escape: " + text);
231 }
232
233 @Nullable
234 public static Character translateEscape(char c) {
235 switch (c) {
236 case 't':
237 return '\t';
238 case 'b':
239 return '\b';
240 case 'n':
241 return '\n';
242 case 'r':
243 return '\r';
244 case '\'':
245 return '\'';
246 case '\"':
247 return '\"';
248 case '\\':
249 return '\\';
250 case '$':
251 return '$';
252 }
253 return null;
254 }
255
256 @NotNull
257 public CompileTimeConstant<?> getRawStringValue(@NotNull String unescapedText, @NotNull JetType expectedType) {
258 CompileTimeConstant<?> error = checkNativeType("\"\"\"...\"\"\"", expectedType, "string", builtIns.getStringType());
259 if (error != null) {
260 return error;
261 }
262
263 return new StringValue(unescapedText.substring(3, unescapedText.length() - 3));
264 }
265
266 @NotNull
267 public CompileTimeConstant<?> getEscapedStringValue(@NotNull List<JetStringTemplateEntry> entries, @NotNull JetType expectedType) {
268 CompileTimeConstant<?> error = checkNativeType("\"...\"", expectedType, "string", builtIns.getStringType());
269 if (error != null) {
270 return error;
271 }
272 final StringBuilder builder = new StringBuilder();
273 final CompileTimeConstant<?>[] result = new CompileTimeConstant<?>[1];
274 for (JetStringTemplateEntry entry : entries) {
275 entry.accept(new JetVisitorVoid() {
276 @Override
277 public void visitStringTemplateEntry(JetStringTemplateEntry entry) {
278 result[0] = new ErrorValue("String templates are not allowed in compile-time constants");
279 }
280
281 @Override
282 public void visitLiteralStringTemplateEntry(JetLiteralStringTemplateEntry entry) {
283 builder.append(entry.getText());
284 }
285
286 @Override
287 public void visitEscapeStringTemplateEntry(JetEscapeStringTemplateEntry entry) {
288 String text = entry.getText();
289 assert text.length() == 2 && text.charAt(0) == '\\';
290 Character character = translateEscape(text.charAt(1));
291 if (character != null) {
292 builder.append(character);
293 }
294 }
295 });
296 if (result[0] != null) {
297 return result[0];
298 }
299 }
300 return new StringValue(builder.toString());
301 }
302
303 @NotNull
304 public CompileTimeConstant<?> getNullValue(@NotNull JetType expectedType) {
305 if (noExpectedType(expectedType) || expectedType.isNullable()) {
306 return NullValue.NULL;
307 }
308 return new ErrorValue("Null can not be a value of a non-null type " + expectedType);
309 }
310
311 private boolean noExpectedType(JetType expectedType) {
312 return expectedType == TypeUtils.NO_EXPECTED_TYPE || KotlinBuiltIns.getInstance().isUnit(expectedType) || ErrorUtils.isErrorType(expectedType);
313 }
314
315 }