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}