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 com.intellij.psi.tree.IElementType;
021 import org.jetbrains.annotations.NotNull;
022 import org.jetbrains.annotations.Nullable;
023 import org.jetbrains.jet.JetNodeTypes;
024 import org.jetbrains.jet.lang.psi.*;
025 import org.jetbrains.jet.lang.types.ErrorUtils;
026 import org.jetbrains.jet.lang.types.JetType;
027 import org.jetbrains.jet.lang.types.TypeConstructor;
028 import org.jetbrains.jet.lang.types.TypeUtils;
029 import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
030 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
031
032 import java.util.List;
033
034 public class CompileTimeConstantResolver {
035 public static final ErrorValue OUT_OF_RANGE = new ErrorValue("The value is out of range");
036
037 private final KotlinBuiltIns builtIns;
038
039 public CompileTimeConstantResolver() {
040 this.builtIns = KotlinBuiltIns.getInstance();
041 }
042
043 @NotNull
044 public CompileTimeConstant<?> getCompileTimeConstant(
045 @NotNull JetConstantExpression expression,
046 @NotNull JetType expectedType
047 ) {
048 IElementType elementType = expression.getNode().getElementType();
049 String text = expression.getNode().getText();
050
051 CompileTimeConstant<?> value;
052 if (elementType == JetNodeTypes.INTEGER_CONSTANT) {
053 value = getIntegerValue(text, expectedType);
054 }
055 else if (elementType == JetNodeTypes.FLOAT_CONSTANT) {
056 value = getFloatValue(text, expectedType);
057 }
058 else if (elementType == JetNodeTypes.BOOLEAN_CONSTANT) {
059 value = getBooleanValue(text, expectedType);
060 }
061 else if (elementType == JetNodeTypes.CHARACTER_CONSTANT) {
062 value = getCharValue(text, expectedType);
063 }
064 else if (elementType == JetNodeTypes.NULL) {
065 value = getNullValue(expectedType);
066 }
067 else {
068 throw new IllegalArgumentException("Unsupported constant: " + expression);
069 }
070 return value;
071 }
072
073
074 @NotNull
075 public CompileTimeConstant<?> getIntegerValue(@NotNull String text, @NotNull JetType expectedType) {
076 return getIntegerValue(parseLongValue(text), expectedType);
077 }
078
079 @NotNull
080 public CompileTimeConstant<?> getIntegerValue(@Nullable Long value, @NotNull JetType expectedType) {
081 if (value == null) {
082 return OUT_OF_RANGE;
083 }
084 if (noExpectedType(expectedType)) {
085 if (Integer.MIN_VALUE <= value && value <= Integer.MAX_VALUE) {
086 return new IntValue(value.intValue());
087 }
088 return new LongValue(value);
089 }
090 Function<Long, ? extends CompileTimeConstant<?>> create;
091 long lowerBound;
092 long upperBound;
093 TypeConstructor constructor = expectedType.getConstructor();
094 if (constructor == builtIns.getInt().getTypeConstructor()) {
095 create = IntValue.CREATE;
096 lowerBound = Integer.MIN_VALUE;
097 upperBound = Integer.MAX_VALUE;
098 }
099 else if (constructor == builtIns.getLong().getTypeConstructor()) {
100 create = LongValue.CREATE;
101 lowerBound = Long.MIN_VALUE;
102 upperBound = Long.MAX_VALUE;
103 }
104 else if (constructor == builtIns.getShort().getTypeConstructor()) {
105 create = ShortValue.CREATE;
106 lowerBound = Short.MIN_VALUE;
107 upperBound = Short.MAX_VALUE;
108 }
109 else if (constructor == builtIns.getByte().getTypeConstructor()) {
110 create = ByteValue.CREATE;
111 lowerBound = Byte.MIN_VALUE;
112 upperBound = Byte.MAX_VALUE;
113 }
114 else {
115 JetTypeChecker typeChecker = JetTypeChecker.INSTANCE;
116 JetType intType = builtIns.getIntType();
117 JetType longType = builtIns.getLongType();
118 if (typeChecker.isSubtypeOf(intType, expectedType)) {
119 return getIntegerValue(value, intType);
120 }
121 else if (typeChecker.isSubtypeOf(longType, expectedType)) {
122 return getIntegerValue(value, longType);
123 }
124 else {
125 return new ErrorValue("An integer literal does not conform to the expected type " + expectedType);
126 }
127 }
128
129 if (value != null && lowerBound <= value && value <= upperBound) {
130 return create.apply(value);
131 }
132 return new ErrorValue("An integer literal does not conform to the expected type " + expectedType);
133 }
134
135 @Nullable
136 public static Long parseLongValue(String text) {
137 try {
138 long value;
139 if (text.startsWith("0x") || text.startsWith("0X")) {
140 String hexString = text.substring(2);
141 value = Long.parseLong(hexString, 16);
142 }
143 else if (text.startsWith("0b") || text.startsWith("0B")) {
144 String binString = text.substring(2);
145 value = Long.parseLong(binString, 2);
146 }
147 else {
148 value = Long.parseLong(text);
149 }
150 return value;
151 }
152 catch (NumberFormatException e) {
153 return null;
154 }
155 }
156
157 @Nullable
158 public static Double parseDoubleValue(String text) {
159 try {
160 return Double.parseDouble(text);
161 }
162 catch (NumberFormatException e) {
163 return null;
164 }
165 }
166
167 @NotNull
168 public CompileTimeConstant<?> getFloatValue(@NotNull String text, @NotNull JetType expectedType) {
169 if (noExpectedType(expectedType)
170 || JetTypeChecker.INSTANCE.isSubtypeOf(builtIns.getDoubleType(), expectedType)) {
171 try {
172 return new DoubleValue(Double.parseDouble(text));
173 }
174 catch (NumberFormatException e) {
175 return OUT_OF_RANGE;
176 }
177 }
178 else if (JetTypeChecker.INSTANCE.isSubtypeOf(builtIns.getFloatType(), expectedType)) {
179 try {
180 return new FloatValue(Float.parseFloat(text));
181 }
182 catch (NumberFormatException e) {
183 return OUT_OF_RANGE;
184 }
185 }
186 else {
187 return new ErrorValue("A floating-point literal does not conform to the expected type " + expectedType);
188 }
189 }
190
191 @Nullable
192 private CompileTimeConstant<?> checkNativeType(String text, JetType expectedType, String title, JetType nativeType) {
193 if (!noExpectedType(expectedType)
194 && !JetTypeChecker.INSTANCE.isSubtypeOf(nativeType, expectedType)) {
195 return new ErrorValue("A " + title + " literal " + text + " does not conform to the expected type " + expectedType);
196 }
197 return null;
198 }
199
200 @NotNull
201 public CompileTimeConstant<?> getBooleanValue(@NotNull String text, @NotNull JetType expectedType) {
202 CompileTimeConstant<?> error = checkNativeType(text, expectedType, "boolean", builtIns.getBooleanType());
203 if (error != null) {
204 return error;
205 }
206 if ("true".equals(text)) {
207 return BooleanValue.TRUE;
208 }
209 else if ("false".equals(text)) {
210 return BooleanValue.FALSE;
211 }
212 throw new IllegalStateException("Must not happen. A boolean literal has text: " + text);
213 }
214
215 @NotNull
216 public CompileTimeConstant<?> getCharValue(@NotNull String text, @NotNull JetType expectedType) {
217 CompileTimeConstant<?> error = checkNativeType(text, expectedType, "character", builtIns.getCharType());
218 if (error != null) {
219 return error;
220 }
221
222 // Strip the quotes
223 if (text.length() < 2 || text.charAt(0) != '\'' || text.charAt(text.length() - 1) != '\'') {
224 return new ErrorValue("Incorrect character literal");
225 }
226 text = text.substring(1, text.length() - 1); // now there're no quotes
227
228 if (text.length() == 0) {
229 return new ErrorValue("Empty character literal");
230 }
231
232 if (text.charAt(0) != '\\') {
233 // No escape
234 if (text.length() == 1) {
235 return new CharValue(text.charAt(0));
236 }
237 return new ErrorValue("Too many characters in a character literal '" + text + "'");
238 }
239 return escapedStringToCharValue(text);
240 }
241
242 @NotNull
243 public static CompileTimeConstant<?> escapedStringToCharValue(@NotNull String text) {
244 assert text.length() > 0 && text.charAt(0) == '\\' : "Only escaped sequences must be passed to this routine: " + text;
245
246 // Escape
247 String escape = text.substring(1); // strip the slash
248 switch (escape.length()) {
249 case 0:
250 // bare slash
251 return illegalEscape(text);
252 case 1:
253 // one-char escape
254 Character escaped = translateEscape(escape.charAt(0));
255 if (escaped == null) {
256 return illegalEscape(text);
257 }
258 return new CharValue(escaped);
259 case 5:
260 // unicode escape
261 if (escape.charAt(0) == 'u') {
262 try {
263 Integer intValue = Integer.valueOf(escape.substring(1), 16);
264 return new CharValue((char) intValue.intValue());
265 } catch (NumberFormatException e) {
266 // Will be reported below
267 }
268 }
269 break;
270 }
271 return illegalEscape(text);
272 }
273
274 private static ErrorValue illegalEscape(String text) {
275 return new ErrorValue("Illegal escape: " + text);
276 }
277
278 @Nullable
279 public static Character translateEscape(char c) {
280 switch (c) {
281 case 't':
282 return '\t';
283 case 'b':
284 return '\b';
285 case 'n':
286 return '\n';
287 case 'r':
288 return '\r';
289 case '\'':
290 return '\'';
291 case '\"':
292 return '\"';
293 case '\\':
294 return '\\';
295 case '$':
296 return '$';
297 }
298 return null;
299 }
300
301 @NotNull
302 public CompileTimeConstant<?> getRawStringValue(@NotNull String unescapedText, @NotNull JetType expectedType) {
303 CompileTimeConstant<?> error = checkNativeType("\"\"\"...\"\"\"", expectedType, "string", builtIns.getStringType());
304 if (error != null) {
305 return error;
306 }
307
308 return new StringValue(unescapedText.substring(3, unescapedText.length() - 3));
309 }
310
311 @NotNull
312 public CompileTimeConstant<?> getEscapedStringValue(@NotNull List<JetStringTemplateEntry> entries, @NotNull JetType expectedType) {
313 CompileTimeConstant<?> error = checkNativeType("\"...\"", expectedType, "string", builtIns.getStringType());
314 if (error != null) {
315 return error;
316 }
317 final StringBuilder builder = new StringBuilder();
318 final CompileTimeConstant<?>[] result = new CompileTimeConstant<?>[1];
319 for (JetStringTemplateEntry entry : entries) {
320 entry.accept(new JetVisitorVoid() {
321 @Override
322 public void visitStringTemplateEntry(JetStringTemplateEntry entry) {
323 result[0] = new ErrorValue("String templates are not allowed in compile-time constants");
324 }
325
326 @Override
327 public void visitLiteralStringTemplateEntry(JetLiteralStringTemplateEntry entry) {
328 builder.append(entry.getText());
329 }
330
331 @Override
332 public void visitEscapeStringTemplateEntry(JetEscapeStringTemplateEntry entry) {
333 String text = entry.getText();
334 assert text.length() == 2 && text.charAt(0) == '\\';
335 Character character = translateEscape(text.charAt(1));
336 if (character != null) {
337 builder.append(character);
338 }
339 }
340 });
341 if (result[0] != null) {
342 return result[0];
343 }
344 }
345 return new StringValue(builder.toString());
346 }
347
348 @NotNull
349 public CompileTimeConstant<?> getNullValue(@NotNull JetType expectedType) {
350 if (noExpectedType(expectedType) || expectedType.isNullable()) {
351 return NullValue.NULL;
352 }
353 return new ErrorValue("Null can not be a value of a non-null type " + expectedType);
354 }
355
356 private boolean noExpectedType(JetType expectedType) {
357 return TypeUtils.noExpectedType(expectedType) || KotlinBuiltIns.getInstance().isUnit(expectedType) || ErrorUtils.isErrorType(expectedType);
358 }
359
360 }