0391430052e9def3b27c0989df2e3668a7333867
[idea/community.git] / plugins / kotlin / j2k / new / src / org / jetbrains / kotlin / nj2k / conversions / LiteralConversion.kt
1 // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2
3 package org.jetbrains.kotlin.nj2k.conversions
4
5
6 import org.jetbrains.annotations.NonNls
7 import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
8 import org.jetbrains.kotlin.nj2k.tree.*
9 import java.math.BigInteger
10
11 class LiteralConversion(context: NewJ2kConverterContext) : RecursiveApplicableConversionBase(context) {
12     override fun applyToElement(element: JKTreeElement): JKTreeElement {
13         if (element !is JKLiteralExpression) return recurse(element)
14
15         val convertedElement = try {
16             element.apply { convertLiteral() }
17         } catch (_: NumberFormatException) {
18             createTodoCall(cannotConvertLiteralMessage(element))
19         }
20
21         return recurse(convertedElement)
22     }
23
24     private fun createTodoCall(@NonNls message: String): JKCallExpressionImpl {
25         val todoMethodSymbol = symbolProvider.provideMethodSymbol("kotlin.TODO")
26         val todoMessageArgument = JKArgumentImpl(JKLiteralExpression("\"$message\"", JKLiteralExpression.LiteralType.STRING))
27
28         return JKCallExpressionImpl(todoMethodSymbol, JKArgumentList(todoMessageArgument))
29     }
30
31     private fun cannotConvertLiteralMessage(element: JKLiteralExpression): String {
32         val literalType = element.type.toString().toLowerCase()
33         val literalValue = element.literal
34         return "Could not convert $literalType literal '$literalValue' to Kotlin"
35     }
36
37     private fun JKLiteralExpression.convertLiteral() {
38         literal = when (type) {
39             JKLiteralExpression.LiteralType.DOUBLE -> toDoubleLiteral()
40             JKLiteralExpression.LiteralType.FLOAT -> toFloatLiteral()
41             JKLiteralExpression.LiteralType.LONG -> toLongLiteral()
42             JKLiteralExpression.LiteralType.INT -> toIntLiteral()
43             JKLiteralExpression.LiteralType.CHAR -> convertCharLiteral()
44             JKLiteralExpression.LiteralType.STRING -> toStringLiteral()
45             else -> return
46         }
47     }
48
49     private fun JKLiteralExpression.toDoubleLiteral() =
50         literal.cleanFloatAndDoubleLiterals().let { text ->
51             if (!text.contains(".") && !text.contains("e", true))
52                 "$text."
53             else text
54         }.let { text ->
55             if (text.endsWith(".")) "${text}0" else text
56         }
57
58
59     private fun JKLiteralExpression.toFloatLiteral() =
60         literal.cleanFloatAndDoubleLiterals().let { text ->
61             if (!text.endsWith("f")) "${text}f"
62             else text
63         }
64
65     private fun JKLiteralExpression.toStringLiteral() =
66         literal.replace("""((?:\\)*)\\([0-3]?[0-7]{1,2})""".toRegex()) { matchResult ->
67             val leadingBackslashes = matchResult.groupValues[1]
68             if (leadingBackslashes.length % 2 == 0)
69                 String.format("%s\\u%04x", leadingBackslashes, Integer.parseInt(matchResult.groupValues[2], 8))
70             else matchResult.value
71         }.replace("""\$([A-Za-z]+|\{)""".toRegex(), "\\\\$0")
72             .replace( "\\f", "\\u000c")
73
74
75     private fun JKLiteralExpression.convertCharLiteral() =
76         literal.replace("""\\([0-3]?[0-7]{1,2})""".toRegex()) {
77             String.format("\\u%04x", Integer.parseInt(it.groupValues[1], 8))
78         }
79
80
81     private fun JKLiteralExpression.toIntLiteral() =
82         literal
83             .cleanIntAndLongLiterals()
84             .convertHexLiteral(isLongLiteral = false)
85             .convertBinaryLiteral(isLongLiteral = false)
86             .convertOctalLiteral(isLongLiteral = false)
87
88
89     private fun JKLiteralExpression.toLongLiteral() =
90         literal
91             .cleanIntAndLongLiterals()
92             .convertHexLiteral(isLongLiteral = true)
93             .convertBinaryLiteral(isLongLiteral = true)
94             .convertOctalLiteral(isLongLiteral = true) + "L"
95
96     private fun String.convertHexLiteral(isLongLiteral: Boolean): String {
97         if (!startsWith("0x", ignoreCase = true)) return this
98         val value = BigInteger(drop(2), 16)
99         return when {
100             isLongLiteral && value.bitLength() > 63 ->
101                 "-0x${value.toLong().toString(16).substring(1)}"
102
103             !isLongLiteral && value.bitLength() > 31 ->
104                 "-0x${value.toInt().toString(16).substring(1)}"
105
106             else -> this
107         }
108     }
109
110     private fun String.convertBinaryLiteral(isLongLiteral: Boolean): String {
111         if (!startsWith("0b", ignoreCase = true)) return this
112         val value = BigInteger(drop(2), 2)
113         return if (isLongLiteral) value.toLong().toString(10) else value.toInt().toString()
114     }
115
116     private fun String.convertOctalLiteral(isLongLiteral: Boolean): String {
117         if (!startsWith("0") || length == 1 || get(1).toLowerCase() == 'x') return this
118         val value = BigInteger(drop(1), 8)
119         return if (isLongLiteral) value.toLong().toString(10) else value.toInt().toString(10)
120     }
121
122     private fun String.cleanFloatAndDoubleLiterals() =
123         replace("L", "", ignoreCase = true)
124             .replace("d", "", ignoreCase = true)
125             .replace(".e", "e", ignoreCase = true)
126             .replace(".f", "", ignoreCase = true)
127             .replace("f", "", ignoreCase = true)
128             .replace("_", "")
129
130     private fun String.cleanIntAndLongLiterals() =
131         replace("l", "", ignoreCase = true)
132             .replace("_", "")
133 }