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 017package org.jetbrains.jet.lang.resolve.constants; 018 019import com.google.common.base.Function; 020import org.jetbrains.annotations.NotNull; 021import org.jetbrains.annotations.Nullable; 022import org.jetbrains.jet.lang.psi.JetEscapeStringTemplateEntry; 023import org.jetbrains.jet.lang.psi.JetLiteralStringTemplateEntry; 024import org.jetbrains.jet.lang.psi.JetStringTemplateEntry; 025import org.jetbrains.jet.lang.psi.JetVisitorVoid; 026import org.jetbrains.jet.lang.types.*; 027import org.jetbrains.jet.lang.types.checker.JetTypeChecker; 028import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; 029 030import java.util.List; 031 032public 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}