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    }