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    }