Skip to content

Commit

Permalink
Constant value replacement. Closes #22
Browse files Browse the repository at this point in the history
  • Loading branch information
andreypfau committed Jul 15, 2024
1 parent a765358 commit 2735856
Show file tree
Hide file tree
Showing 13 changed files with 263 additions and 26 deletions.
17 changes: 14 additions & 3 deletions src/main/kotlin/org/ton/intellij/func/eval/FuncValue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ data class FuncTensorValue(val values: List<FuncValue?>) : FuncValue {
}
}

@OptIn(ExperimentalUnsignedTypes::class)
val FuncLiteralExpression.value: FuncValue
get() {
if (trueKeyword != null) {
Expand All @@ -50,18 +51,21 @@ val FuncLiteralExpression.value: FuncValue
if (isNegative) {
text = text.substring(1)
}
val integer = try {
var integer = try {
if (text.startsWith("0x") || text.startsWith("0X")) {
BigInteger(text.substring(2), 16)
} else if (text.startsWith("0b") || text.startsWith("0B")) {
BigInteger(text.substring(2), 2)
} else {
BigInteger(integerLiteral.text)
BigInteger(text)
}
} catch (e: NumberFormatException) {
return FuncIntValue(BigInteger.ZERO)
}
return if (isNegative) FuncIntValue(-integer) else FuncIntValue(integer)
if (isNegative) {
integer = integer.negate()
}
return FuncIntValue(integer)
}
val stringLiteral = stringLiteral
if (stringLiteral != null) {
Expand All @@ -72,6 +76,13 @@ val FuncLiteralExpression.value: FuncValue
return FuncIntValue(BigInteger.ZERO) // TODO: Slice type
}
when (tag) {
'u' -> {
if (text.length <= 3) return FuncIntValue(BigInteger.ZERO)
val rawValue = text.substring(1, text.length - 2)
val intValue = BigInteger(Hex.encodeHexString(rawValue.encodeToByteArray()), 16)
return FuncIntValue(intValue)
}

'h' -> {
if (text.length <= 3) return FuncIntValue(BigInteger.ZERO)
val rawValue = text.substring(1, text.length - 2)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.ton.intellij.func.inspection.style

import com.intellij.codeInspection.*
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.ton.intellij.func.eval.FuncConstantExpressionEvaluator
import org.ton.intellij.func.eval.FuncIntValue
import org.ton.intellij.func.inspection.FuncInspectionBase
import org.ton.intellij.func.psi.*
import org.ton.intellij.util.InspectionBundle

private const val MAX_RESULT_LENGTH_TO_DISPLAY = 40

class FuncConstantExpressionInspection : FuncInspectionBase() {
override fun buildFuncVisitor(
holder: ProblemsHolder,
session: LocalInspectionToolSession,
): FuncVisitor = object : FuncVisitor() {
override fun visitExpression(expression: FuncExpression) {
val parent = expression.parent
if (expression is FuncLiteralExpression && expression.integerLiteral != null) return
if (parent is FuncBinExpression && (parent.binaryOp.eq == null || parent.right != expression)) return
if (parent is FuncExpression && parent !is FuncBinExpression) return
val value = FuncConstantExpressionEvaluator.compute(holder.project, expression) ?: return
val valueText = buildString {
if (value is FuncIntValue && expression is FuncLiteralExpression && expression.stringLiteral != null) {
append("0x")
val hex = value.value.toString(16)
append(hex)
} else {
append(value.toString())
}
}
val message = if (valueText.length > MAX_RESULT_LENGTH_TO_DISPLAY) {
InspectionBundle.message("fix.constant.expression.name.short")
} else {
InspectionBundle.message("fix.constant.expression.name", valueText)
}
val range = TextRange.from(expression.startOffsetInParent, expression.textLength)
holder.registerProblem(
parent,
message,
ProblemHighlightType.INFORMATION,
range,
FuncComputeConstantExpressionFix(expression, valueText)
)
}
}
}

class FuncComputeConstantExpressionFix(
element: PsiElement,
private val valueText: String
) : LocalQuickFixAndIntentionActionOnPsiElement(element), LocalQuickFix {
override fun getFamilyName(): String =
InspectionBundle.message("fix.constant.expression.family.name")

override fun getText(): String {
return if (valueText.length < MAX_RESULT_LENGTH_TO_DISPLAY) {
InspectionBundle.message("fix.constant.expression.name", valueText)
} else {
InspectionBundle.message("fix.constant.expression.name.short")
}
}

override fun invoke(
project: Project,
file: PsiFile,
editor: Editor?,
startElement: PsiElement,
endElement: PsiElement
) {
startElement.replace(FuncPsiFactory[project].createExpression(valueText))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.ton.intellij.func.intentions

import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.ton.intellij.func.eval.FuncConstantExpressionEvaluator
import org.ton.intellij.func.eval.FuncIntValue
import org.ton.intellij.func.psi.FuncExpression
import org.ton.intellij.func.psi.FuncLiteralExpression
import org.ton.intellij.func.psi.FuncPsiFactory
import org.ton.intellij.util.InspectionBundle

class FuncConstantExpressionIntention : PsiElementBaseIntentionAction() {
override fun getFamilyName(): String {
return InspectionBundle.message("fix.constant.expression.family.name")
}

override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean {
if (element !is FuncExpression) return false
if (element is FuncLiteralExpression && element.integerLiteral != null) return false
val parent = element.parent
if (parent is FuncExpression) return false
return true
}

override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
val value = FuncConstantExpressionEvaluator.compute(project, element as? FuncExpression ?: return) ?: return
val valueText = buildString {
if (value is FuncIntValue && element is FuncLiteralExpression && element.stringLiteral != null) {
append("0x")
val hex = value.value.toString(16)
append(hex)
} else {
append(value.toString())
}
}
element.replace(FuncPsiFactory[project].createExpression(valueText))
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
package org.ton.intellij.tact.eval

import com.intellij.openapi.project.Project
import org.ton.intellij.tact.psi.*
import org.ton.intellij.tvm.math.divModFloor
import org.ton.intellij.util.recursionGuard
import java.math.BigInteger

fun TactExpression.evaluate(): TactValue? = TactEvaluator.evaluate(this)

object TactEvaluator {
fun evaluate(expression: TactExpression): TactValue? {
object TactConstantExpressionEvaluator {
fun compute(project: Project, expression: TactExpression): TactValue? {
return when (expression) {
is TactIntegerExpression -> expression.eval()
is TactUnaryExpression -> expression.eval()
is TactBinExpression -> expression.eval()
is TactReferenceExpression -> expression.eval()
is TactParenExpression -> compute(project, expression.expression ?: return null)
else -> null
}
}

private fun TactExpression.compute(): TactValue? = compute(this.project, this)

@Suppress("HardCodedStringLiteral")
private fun TactIntegerExpression.eval(): TactValue? {
val text = integerLiteral.text.replace("_", "")
Expand All @@ -37,7 +40,7 @@ object TactEvaluator {
}

private fun TactUnaryExpression.eval(): TactValue? {
val value = expression?.evaluate() ?: return null
val value = expression?.compute() ?: return null
when (value) {
is TactIntValue -> when {
plus != null -> return value
Expand All @@ -55,8 +58,8 @@ object TactEvaluator {
}

private fun TactBinExpression.eval(): TactValue? {
val rightValue = right?.evaluate() ?: return null
val leftValue = left.evaluate() ?: return null
val rightValue = right?.compute() ?: return null
val leftValue = left.compute() ?: return null
val op = binOp
when {
op.plus != null -> {
Expand Down Expand Up @@ -179,8 +182,8 @@ object TactEvaluator {
private fun TactReferenceExpression.eval(): TactValue? = recursionGuard(this, memoize = false) {
val resolved = reference?.resolve() ?: return@recursionGuard null
when (resolved) {
is TactLetStatement -> resolved.expression?.evaluate()
is TactConstant -> resolved.expression?.evaluate()
is TactLetStatement -> resolved.expression?.compute()
is TactConstant -> resolved.expression?.compute()
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.refactoring.suggested.endOffset
import org.ton.intellij.tact.eval.evaluate
import org.ton.intellij.tact.eval.TactConstantExpressionEvaluator
import org.ton.intellij.tact.psi.TactConstant
import org.ton.intellij.tact.psi.TactIntegerExpression

Expand All @@ -24,7 +24,9 @@ class TactConstantHintsProvider : InlayHintsProvider {
if (expression is TactIntegerExpression) {
return
}
val value = constant.expression?.evaluate() ?: return
val value = constant.expression?.let {
TactConstantExpressionEvaluator.compute(it.project, it)
} ?: return
sink.addPresentation(InlineInlayPosition(constant.identifier.endOffset, true), hasBackground = true) {
val text = buildString {
append("= ")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.ton.intellij.tact.inspections.numeric

import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import org.ton.intellij.tact.eval.TactConstantExpressionEvaluator
import org.ton.intellij.tact.eval.TactIntValue
import org.ton.intellij.tact.eval.evaluate
import org.ton.intellij.tact.inspections.TactLocalInspectionTool
import org.ton.intellij.tact.psi.TactBinExpression
import org.ton.intellij.tact.psi.TactExpression
Expand Down Expand Up @@ -30,14 +31,14 @@ class TactDivideByZeroInspection : TactLocalInspectionTool() {
if (binaryOp.div == null) {
return
}
if (o.right?.isZero() == true) {
if (o.right?.isZero(holder.project) == true) {
holder.registerProblem(o, InspectionBundle.message("numeric.divide_by_zero"))
}
}
}

private fun TactExpression.isZero(): Boolean {
val value = evaluate()
private fun TactExpression.isZero(project: Project): Boolean {
val value = TactConstantExpressionEvaluator.compute(project, this)
return value is TactIntValue && value.value == BigInteger.ZERO
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.ton.intellij.tact.inspections.style

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiFile
import org.ton.intellij.tact.eval.TactConstantExpressionEvaluator
import org.ton.intellij.tact.inspections.TactLocalInspectionTool
import org.ton.intellij.tact.psi.TactExpression
import org.ton.intellij.tact.psi.TactIntegerExpression
import org.ton.intellij.tact.psi.TactPsiFactory
import org.ton.intellij.tact.psi.TactVisitor
import org.ton.intellij.util.InspectionBundle

private const val MAX_RESULT_LENGTH_TO_DISPLAY = 40

class TactConstantExpressionInspection : TactLocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor =
ComputeConstantVisitor(holder)

private class ComputeConstantVisitor(
val holder: ProblemsHolder
) : TactVisitor() {
override fun visitExpression(expression: TactExpression) {
if (expression is TactIntegerExpression) return
val parent = expression.parent
if (parent is TactExpression) return
val value = TactConstantExpressionEvaluator.compute(holder.project, expression) ?: return
val valueText = value.toString()
val message = if (valueText.length > MAX_RESULT_LENGTH_TO_DISPLAY) {
InspectionBundle.message("fix.constant.expression.name.short")
} else {
InspectionBundle.message("fix.constant.expression.name", valueText)
}
val range = TextRange.from(expression.startOffsetInParent, expression.textLength)
holder.registerProblem(
parent,
message,
ProblemHighlightType.INFORMATION,
range,
TactComputeConstantExpressionFix(expression, valueText)
)
}
}

private class TactComputeConstantExpressionFix(
element: PsiElement,
private val valueText: String
) : LocalQuickFixAndIntentionActionOnPsiElement(element), LocalQuickFix {
override fun getFamilyName(): String =
InspectionBundle.message("fix.constant.expression.family.name")

override fun getText(): String {
return if (valueText.length < MAX_RESULT_LENGTH_TO_DISPLAY) {
InspectionBundle.message("fix.constant.expression.name", valueText)
} else {
InspectionBundle.message("fix.constant.expression.name.short")
}
}

override fun invoke(
project: Project,
file: PsiFile,
editor: Editor?,
startElement: PsiElement,
endElement: PsiElement
) {
startElement.replace(TactPsiFactory[project].createExpression(valueText))
}
}
}
18 changes: 11 additions & 7 deletions src/main/kotlin/org/ton/intellij/tact/psi/TactPsiFactory.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package org.ton.intellij.tact.psi

import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.util.LocalTimeCounter
import org.ton.intellij.tact.TactFileType
import org.ton.intellij.util.descendantOfTypeStrict

@Service(Service.Level.PROJECT)
class TactPsiFactory(
private val project: Project,
private val markGenerated: Boolean = true,
private val eventSystemEnabled: Boolean = false
private val project: Project
) {
fun createFile(text: CharSequence): TactFile = createPsiFile(text) as TactFile

Expand All @@ -20,15 +19,20 @@ class TactPsiFactory(
"DUMMY.tact",
TactFileType,
text,
LocalTimeCounter.currentTime(),
eventSystemEnabled,
markGenerated
)

fun createIdentifier(name: String): PsiElement =
createFromText<TactStruct>("struct $name {}")?.identifier ?: error("Failed to create identifier")

fun createExpression(text: String): PsiElement =
createFromText<TactExpression>("fun dummy() { $text; }") ?: error("Failed to create expression")

private inline fun <reified T : TactElement> createFromText(
code: CharSequence
): T? = createFile(code).descendantOfTypeStrict()

companion object {
operator fun get(project: Project) =
requireNotNull(project.getService(TactPsiFactory::class.java))
}
}
Loading

0 comments on commit 2735856

Please sign in to comment.