All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.io.ksmt.solver.yices.KYicesExprConverter.kt Maven / Gradle / Ivy

There is a newer version: 0.5.26
Show newest version
package io.ksmt.solver.yices

import com.sri.yices.Constructor
import com.sri.yices.Terms
import com.sri.yices.Terms.Component
import com.sri.yices.Terms.NULL_TERM
import com.sri.yices.Types
import io.ksmt.KContext
import io.ksmt.decl.KConstDecl
import io.ksmt.decl.KDecl
import io.ksmt.expr.KExpr
import io.ksmt.expr.printer.ExpressionPrinter
import io.ksmt.expr.transformer.KNonRecursiveTransformer
import io.ksmt.expr.transformer.KTransformer
import io.ksmt.expr.transformer.KTransformerBase
import io.ksmt.solver.util.ExprConversionResult
import io.ksmt.solver.util.KExprConverterBase
import io.ksmt.sort.KArithSort
import io.ksmt.sort.KArray2Sort
import io.ksmt.sort.KArray3Sort
import io.ksmt.sort.KArrayNSort
import io.ksmt.sort.KArraySort
import io.ksmt.sort.KArraySortBase
import io.ksmt.sort.KBoolSort
import io.ksmt.sort.KBvSort
import io.ksmt.sort.KIntSort
import io.ksmt.sort.KRealSort
import io.ksmt.sort.KSort
import io.ksmt.sort.KUninterpretedSort
import io.ksmt.utils.BvUtils.bvMaxValueUnsigned
import io.ksmt.utils.BvUtils.bvZero
import io.ksmt.utils.uncheckedCast

open class KYicesExprConverter(
    private val ctx: KContext,
    private val yicesCtx: KYicesContext
) : KExprConverterBase() {
    fun  YicesTerm.convert(expectedSort: T): KExpr =
        convertFromNative()
            .ensureSort(expectedSort)

    override fun findConvertedNative(expr: YicesTerm): KExpr<*>? =
        yicesCtx.findConvertedExpr(expr)

    override fun saveConvertedNative(native: YicesTerm, converted: KExpr<*>) {
        yicesCtx.saveConvertedExpr(native, converted)
    }

    open fun convertDecl(decl: YicesTerm): KDecl<*> = yicesCtx.convertDecl(decl) {
        generateFreshDecl(decl)
    }

    open fun convertVar(variable: YicesTerm): KDecl<*> = yicesCtx.convertVar(variable) {
        generateFreshDecl(variable)
    }

    open fun findOrCreateFunctionDecl(term: YicesTerm): KDecl<*> =
        yicesCtx.findConvertedDecl(term)
            ?: yicesCtx.findConvertedVar(term)
            ?: convertDecl(term)

    open fun generateFreshDecl(term: YicesTerm): KDecl<*> {
        val name = Terms.getName(term) ?: "yices"
        val sort = Terms.typeOf(term)

        if (!Terms.isFunction(term)) {
            return ctx.mkFreshConstDecl(name, convertSort(sort))
        }

        val childrenTypes = Types.children(sort).map { convertSort(it) }
        return ctx.mkFreshFuncDecl(name, childrenTypes.last(), childrenTypes.dropLast(1))
    }

    open fun convertSort(sort: YicesSort): KSort = yicesCtx.convertSort(sort) {
        with(ctx) {
            when (sort) {
                yicesCtx.int -> intSort
                yicesCtx.real -> realSort
                yicesCtx.bool -> boolSort
                else -> when {
                    Types.isBitvector(sort) -> mkBvSort(Types.bvSize(sort).toUInt())
                    Types.isUninterpreted(sort) -> mkUninterpretedSort(
                        Types.getName(sort) ?: error("Unexpected null name")
                    )

                    Types.isFunction(sort) -> {
                        val sortChildren = Types.children(sort)
                        val domain = sortChildren.dropLast(1).map { convertSort(it) }
                        val range = convertSort(sortChildren.last())

                        mkAnyArraySort(domain, range)
                    }

                    else -> error("Not supported sort")
                }
            }
        }
    }

    override fun convertNativeExpr(expr: YicesTerm): ExprConversionResult {
        return when {
            Terms.isAtomic(expr) -> convertAtomic(expr)
            Terms.isProjection(expr) -> convertProjection(expr)
            Terms.isSum(expr) -> convertSum(expr)
            Terms.isBvSum(expr) -> convertBvSum(expr)
            Terms.isProduct(expr) -> convertProduct(expr)
            Terms.isComposite(expr) -> convertComposite(expr)
            else -> error("Invalid term")
        }
    }

    private fun convertAtomic(expr: YicesTerm) = with(ctx) {
        when (Terms.constructor(expr)) {
            Constructor.BOOL_CONSTANT -> convert { Terms.boolConstValue(expr).expr }
            Constructor.ARITH_CONSTANT -> convert {
                val value = Terms.arithConstValue(expr)

                mkArithNum(value)
            }

            Constructor.BV_CONSTANT -> convert {
                mkBv(Terms.bvConstValue(expr), Terms.bitSize(expr).toUInt())
            }

            Constructor.VARIABLE -> convert {
                val convertedDecl = convertVar(expr)
                check(convertedDecl is KConstDecl<*>) { "Unexpected variable $convertedDecl" }

                convertedDecl.apply()
            }

            Constructor.UNINTERPRETED_TERM -> convert {
                val convertedDecl = convertDecl(expr)
                check(convertedDecl is KConstDecl<*>) { "Unexpected declaration $convertedDecl" }

                convertedDecl.apply()
            }

            Constructor.SCALAR_CONSTANT -> convert {
                val idx = Terms.scalarConstantIndex(expr)
                val valueIdx = yicesCtx.convertUninterpretedSortValueIndex(idx)

                val sort = convertSort(Terms.typeOf(expr))

                mkUninterpretedSortValue(sort as KUninterpretedSort, valueIdx)
            }

            else -> error("Not supported term ${Terms.toString(expr)}")
        }
    }

    private fun convertProjection(expr: YicesTerm) = with(ctx) {
        val constructor: Constructor = Terms.constructor(expr)
        check(constructor == Constructor.BIT_TERM) { "Not supported term ${Terms.toString(expr)}" }

        expr.convert(arrayOf(Terms.projArg(expr))) { bv: KExpr ->
            BvBitExtractRoot(bv, Terms.projIndex(expr))
        }
    }

    private inline fun  YicesTerm.convertComponents(
        getComponent: (YicesTerm, Int) -> Component,
        expectedTermSort: S,
        wrapConstant: KContext.(K, S) -> KExpr<*>,
        mkComponentTerm: KContext.(KExpr, KExpr) -> KExpr,
        reduceComponentTerms: KContext.(KExpr, KExpr) -> KExpr
    ): ExprConversionResult {
        val simpleConstants = mutableListOf()
        val termConstants = mutableListOf()
        val terms = mutableListOf()

        for (i in 0 until Terms.numChildren(this)) {
            val component = getComponent(this, i)
            if (component.term == NULL_TERM) {
                simpleConstants.add(component.constValue)
            } else {
                termConstants.add(component.constValue)
                terms.add(component.term)
            }
        }

        return convertList(terms.toTypedArray()) { convertedTerms: List> ->
            val wrappedSimpleConstants = simpleConstants.map { ctx.wrapConstant(it, expectedTermSort) }
            val wrappedTermConstants = termConstants.map { ctx.wrapConstant(it, expectedTermSort) }

            val expectedSort = (wrappedSimpleConstants + wrappedTermConstants + convertedTerms)
                .map { it.sort }
                .reduce { acc, exprSort -> mergeSorts(acc, exprSort) }

            val expressions = mutableListOf>()
            wrappedSimpleConstants.mapTo(expressions) { it.ensureSort(expectedSort).uncheckedCast() }

            convertedTerms.zip(wrappedTermConstants) { term, const ->
                val termWithCorrectSort: KExpr = term.ensureSort(expectedSort).uncheckedCast()
                val constWithCorrectSort: KExpr = const.ensureSort(expectedSort).uncheckedCast()
                expressions += ctx.mkComponentTerm(constWithCorrectSort, termWithCorrectSort)
            }

            expressions.reduce { acc, e -> ctx.reduceComponentTerms(acc, e) }
        }
    }

    private fun convertSum(expr: YicesTerm): ExprConversionResult = expr.convertComponents<_, KArithSort, _>(
        getComponent = { _, idx -> Terms.sumComponent(expr, idx) },
        expectedTermSort = null,
        wrapConstant = { value, _ -> mkArithNum(value) },
        mkComponentTerm = { const, term -> mkArithMul(const, term) },
        reduceComponentTerms = { acc, term -> mkArithAdd(acc, term) }
    )

    private fun convertBvSum(expr: YicesTerm): ExprConversionResult = expr.convertComponents<_, KBvSort, _>(
        getComponent = { _, idx -> Terms.sumbvComponent(expr, idx) },
        expectedTermSort = ctx.mkBvSort(Terms.bitSize(expr).toUInt()),
        wrapConstant = { value, sort -> mkBv(value.toBooleanArray(), sort) },
        mkComponentTerm = { const, term -> mkBvMulExpr(const, term) },
        reduceComponentTerms = { acc, term -> mkBvAddExpr(acc, term) }
    )

    private fun convertProduct(expr: YicesTerm): ExprConversionResult =
        if (Terms.isBitvector(expr)) {
            convertBvProduct(expr)
        } else {
            convertRealProduct(expr)
        }

    private inner class BvPowerWrapper(val value: Int, override val sort: T) : KExpr(ctx) {
        override fun print(printer: ExpressionPrinter) {
            printer.append("(wrap $value)")
        }

        override fun accept(transformer: KTransformerBase): KExpr = error("Transformers are not used for wrapper")
        override fun internEquals(other: Any): Boolean = error("Interning is not used for wrapper")
        override fun internHashCode(): Int = error("Interning is not used for wrapper")
    }

    private fun convertBvProduct(expr: YicesTerm): ExprConversionResult = expr.convertComponents<_, KBvSort, _>(
        getComponent = { _, idx -> Terms.productComponent(expr, idx) },
        expectedTermSort = ctx.mkBvSort(Terms.bitSize(expr).toUInt()),
        wrapConstant = { value, sort -> BvPowerWrapper(value, sort) },
        mkComponentTerm = { const, term -> mkBvPow(term, (const as BvPowerWrapper<*>).value) },
        reduceComponentTerms = { acc, term -> mkBvMulExpr(acc, term) }
    )

    private fun convertRealProduct(expr: YicesTerm): ExprConversionResult = expr.convertComponents<_, KArithSort, _>(
        getComponent = { _, idx -> Terms.productComponent(expr, idx) },
        expectedTermSort = ctx.realSort,
        wrapConstant = { value, _ -> mkIntNum(value) },
        mkComponentTerm = { const, term -> mkArithPower(term, const) },
        reduceComponentTerms = { acc, term -> mkArithMul(acc, term) }
    )

    private fun  KContext.mkBv(value: BooleanArray, sort: S): KExpr =
        mkBv(value, sort.sizeBits).uncheckedCast()

    private fun KContext.mkBvPow(base: KExpr, exp: Int): KExpr {
        check(exp > 0)

        if (exp == 1)
            return base

        val t = mkBvPow(base, exp / 2).let {
            mkBvMulExpr(it, it)
        }

        return if (exp % 2 == 0)
            t
        else
            mkBvMulExpr(base, t)
    }

    private fun mkFloor(arg: KExpr): KExpr = ctx.mkRealToInt(arg)

    private fun mkCeil(arg: KExpr): KExpr = with(ctx) {
        val floor = mkFloor(arg)
        val realFloor = mkIntToReal(floor)
        val condition = mkEq(arg, realFloor)

        mkIte(condition, floor, mkArithAdd(floor, mkIntNum(1)))
    }

    private fun mkIDiv(arg0: KExpr, arg1: KExpr): KExpr = with(ctx) {
        val condition = mkArithGt(arg1, ctx.mkRealNum(0))
        val div = mkArithDiv(arg0, arg1)

        mkIte(condition, mkFloor(div), mkCeil(div))
    }

    private fun converterMkIte(
        condition: KExpr,
        trueBranch: KExpr,
        falseBranch: KExpr
    ): KExpr = with(ctx) {
        if (trueBranch is BvBitExtractExpr || falseBranch is BvBitExtractExpr) {
            return BvBitExtractIte(condition, trueBranch.uncheckedCast(), falseBranch.uncheckedCast()).uncheckedCast()
        }

        val expectedSort = mergeSorts(trueBranch.sort, falseBranch.sort)
        return mkIte(
            condition.ensureSort(boolSort),
            trueBranch.ensureSort(expectedSort),
            falseBranch.ensureSort(expectedSort)
        )
    }

    private fun converterMkEq(
        lhs: KExpr,
        rhs: KExpr
    ): KExpr = with(ctx) {
        if (lhs is BvBitExtractExpr || rhs is BvBitExtractExpr) {
            return BvBitExtractEq(lhs.uncheckedCast(), rhs.uncheckedCast())
        }

        val expectedSort = mergeSorts(lhs.sort, rhs.sort)
        return mkEq(lhs.ensureSort(expectedSort), rhs.ensureSort(expectedSort))
    }

    private fun converterMkDistinct(args: List>): KExpr = with(ctx) {
        if (args.any { it is BvBitExtractExpr }) {
            return BvBitExtractDistinct(args.uncheckedCast())
        }

        val expectedSort = args.map { it.sort }.reduce(::mergeSorts)
        return mkDistinct(args.map { it.ensureSort(expectedSort) })
    }

    private fun converterMkNot(arg: KExpr): KExpr = with(ctx) {
        if (arg is BvBitExtractExpr) {
            return BvBitExtractNot(arg)
        }

        return mkNot(arg)
    }

    private fun converterMkOr(args: List>): KExpr = with(ctx) {
        if (args.any { it is BvBitExtractExpr }) {
            return BvBitExtractOr(args)
        }

        return mkOr(args)
    }

    private fun converterMkXor(args: List>): KExpr = with(ctx) {
        if (args.any { it is BvBitExtractExpr }) {
            return BvBitExtractXor(args)
        }

        return args.reduce(::mkXor)
    }

    private fun convertBvArray(args: List>): KExpr =
        processBvArrayChunked(args.asReversed())

    private fun converterMkFunctionApp(decl: KDecl<*>, args: List>): KExpr<*> = with(ctx) {
        check(decl.argSorts.size == args.size) { "Arguments size mismatch" }
        val argsWithCorrectSorts = args.zip(decl.argSorts) { arg, sort -> arg.ensureSort(sort) }
        decl.apply(argsWithCorrectSorts)
    }

    private fun YicesTerm.convertFunctionApp(args: Array) = convertFunctionExpression(
        args = args,
        functionExpression = { decl, convertedArgs -> converterMkFunctionApp(decl, convertedArgs) },
        arrayExpression = { array, convertedArgs -> ctx.mkAnyArraySelect(array, convertedArgs) }
    )

    private fun YicesTerm.convertFunctionStore(args: Array) = convertFunctionExpression(
        args = args,
        functionExpression = { decl, convertedArgs ->
            val sort = ctx.mkAnyArraySort(decl.argSorts, decl.sort)
            val array = ctx.mkFunctionAsArray(sort, decl.uncheckedCast())
            ctx.mkAnyArrayStore(
                array = array,
                indices = convertedArgs.dropLast(1),
                value = convertedArgs.last()
            )
        },
        arrayExpression = { array, convertedArgs ->
            ctx.mkAnyArrayStore(
                array = array,
                indices = convertedArgs.dropLast(1),
                value = convertedArgs.last()
            )
        }
    )

    private inline fun YicesTerm.convertFunctionExpression(
        args: Array,
        functionExpression: (KDecl<*>, List>) -> KExpr<*>,
        arrayExpression: (KExpr>, List>) -> KExpr<*>
    ): ExprConversionResult {
        val functionTerm = args.first()
        val functionIsDecl = Terms.isAtomic(functionTerm)

        val appArgs = if (functionIsDecl) {
            // convert function decl separately
            args.copyOfRange(fromIndex = 1, toIndex = args.size)
        } else {
            // convert function expression as part of arguments
            args
        }

        return convertList(appArgs) { convertedArgs: List> ->
            if (functionIsDecl) {
                val funcDecl = findOrCreateFunctionDecl(functionTerm)
                if (convertedArgs.isNotEmpty() && funcDecl is KConstDecl<*> && funcDecl.sort is KArraySortBase<*>) {
                    val array: KExpr> = ctx.mkConstApp(funcDecl).uncheckedCast()
                    arrayExpression(array, convertedArgs)
                } else {
                    functionExpression(funcDecl, convertedArgs)
                }
            } else {
                val array: KExpr> = convertedArgs.first().uncheckedCast()
                val arrayArgs = convertedArgs.drop(1)
                arrayExpression(array, arrayArgs)
            }
        }
    }

    @Suppress("LongMethod", "ComplexMethod")
    private fun convertComposite(expr: YicesTerm) = with(ctx) {
        val yicesArgs = Terms.children(expr).toTypedArray()

        when (Terms.constructor(expr)) {
            Constructor.APP_TERM -> expr.convertFunctionApp(yicesArgs)
            Constructor.UPDATE_TERM -> expr.convertFunctionStore(yicesArgs)

            Constructor.EQ_TERM -> expr.convert(yicesArgs, ::converterMkEq)
            Constructor.DISTINCT_TERM -> expr.convertList(yicesArgs, ::converterMkDistinct)
            Constructor.ITE_TERM -> expr.convert(yicesArgs, ::converterMkIte)

            Constructor.FORALL_TERM -> {
                expr.convert(yicesArgs.takeLast(1).toTypedArray()) { body: KExpr ->
                    val bounds = yicesArgs.dropLast(1).map { convertVar(it) }
                    ctx.mkUniversalQuantifier(body.ensureSort(boolSort), bounds)
                }
            }

            Constructor.LAMBDA_TERM -> {
                expr.convert(yicesArgs.takeLast(1).toTypedArray()) { body: KExpr ->
                    val bounds = yicesArgs.dropLast(1).map { convertVar(it) }
                    mkAnyArrayLambda(bounds, body.eliminateBitExtract())
                }
            }

            Constructor.NOT_TERM -> expr.convert(yicesArgs, ::converterMkNot)
            Constructor.OR_TERM -> expr.convertList(yicesArgs, ::converterMkOr)
            Constructor.XOR_TERM -> expr.convertList(yicesArgs, ::converterMkXor)

            Constructor.BV_ARRAY -> expr.convertList(yicesArgs, ::convertBvArray)

            Constructor.BV_DIV -> expr.convert(yicesArgs, ::mkBvUnsignedDivExpr)
            Constructor.BV_REM -> expr.convert(yicesArgs, ::mkBvUnsignedRemExpr)
            Constructor.BV_SDIV -> expr.convert(yicesArgs, ::mkBvSignedDivExpr)
            Constructor.BV_SREM -> expr.convert(yicesArgs, ::mkBvSignedRemExpr)
            Constructor.BV_SMOD -> expr.convert(yicesArgs, ::mkBvSignedModExpr)
            Constructor.BV_SHL -> expr.convert(yicesArgs, ::mkBvShiftLeftExpr)
            Constructor.BV_LSHR -> expr.convert(yicesArgs, ::mkBvLogicalShiftRightExpr)
            Constructor.BV_ASHR -> expr.convert(yicesArgs, ::mkBvArithShiftRightExpr)
            Constructor.BV_GE_ATOM -> expr.convert(yicesArgs, ::mkBvUnsignedGreaterOrEqualExpr)
            Constructor.BV_SGE_ATOM -> expr.convert(yicesArgs, ::mkBvSignedGreaterOrEqualExpr)

            Constructor.ARITH_GE_ATOM -> expr.convert(yicesArgs) { lhs: KExpr, rhs: KExpr ->
                val expectedSort: KArithSort = mergeSorts(lhs.sort, rhs.sort).uncheckedCast()
                mkArithGe(lhs.ensureSort(expectedSort), rhs.ensureSort(expectedSort))
            }

            Constructor.ABS -> expr.convert(yicesArgs) { x: KExpr ->
                val condition = mkArithUnaryExpr(
                    expr = x,
                    intExpr = { mkArithGe(it, mkIntNum(0)) },
                    realExpr = { mkArithGe(it, mkRealNum(0)) }
                )
                mkIte(condition, x, mkArithUnaryMinus(x))
            }

            Constructor.CEIL -> expr.convert(yicesArgs) { x: KExpr ->
                mkArithUnaryExpr(
                    expr = x,
                    intExpr = { it },
                    realExpr = { mkCeil(it) }
                )
            }

            Constructor.FLOOR -> expr.convert(yicesArgs) { x: KExpr ->
                mkArithUnaryExpr(
                    expr = x,
                    intExpr = { it },
                    realExpr = { mkFloor(it) }
                )
            }

            Constructor.RDIV -> expr.convert(yicesArgs) { lhs: KExpr, rhs: KExpr ->
                mkArithDiv(lhs.ensureSort(realSort), rhs.ensureSort(realSort))
            }

            Constructor.IDIV -> expr.convert(yicesArgs) { lhs: KExpr, rhs: KExpr ->
                mkArithBinaryExpr(
                    lhs = lhs, rhs = rhs,
                    intExpr = { l, r -> mkArithDiv(l, r) },
                    realExpr = { l, r -> mkIDiv(l, r) }
                )
            }

            Constructor.IMOD -> expr.convert(yicesArgs) { lhs: KExpr, rhs: KExpr ->
                mkArithBinaryExpr(
                    lhs = lhs, rhs = rhs,
                    intExpr = { l, r -> mkIntMod(l, r) },
                    realExpr = { l, r ->
                        val integerQuotient = mkIDiv(l, r).ensureSort(realSort)
                        mkArithSub(l, mkArithMul(r, integerQuotient))
                    }
                )
            }

            Constructor.IS_INT_ATOM -> expr.convert(yicesArgs) { arg: KExpr ->
                mkArithUnaryExpr(
                    expr = arg,
                    intExpr = { trueExpr },
                    realExpr = { mkRealIsInt(it) }
                )
            }

            Constructor.DIVIDES_ATOM -> expr.convert(yicesArgs) { lhs: KExpr, rhs: KExpr ->
                mkArithBinaryExpr(
                    lhs = lhs, rhs = rhs,
                    intExpr = { l, r -> mkIntRem(r, l) eq mkIntNum(0) },
                    realExpr = { l, r -> mkRealIsInt(mkArithDiv(r, l)) }
                )
            }

            Constructor.ARITH_ROOT_ATOM -> TODO("ARITH_ROOT conversion is not supported")
            Constructor.TUPLE_TERM -> TODO("Tuple conversion is not supported")
            Constructor.CONSTRUCTOR_ERROR -> error("Constructor error")
            else -> error("Unexpected constructor ${Terms.constructor(expr)}")
        }
    }

    private inline fun  KContext.mkArithUnaryExpr(
        expr: KExpr,
        intExpr: (KExpr) -> KExpr,
        realExpr: (KExpr) -> KExpr
    ) = when (expr.sort) {
        intSort -> intExpr(expr.uncheckedCast())
        realSort -> realExpr(expr.uncheckedCast())
        else -> error("Unexpected arith expr ${expr}")
    }

    private inline fun KContext.mkArithBinaryExpr(
        lhs: KExpr, rhs: KExpr,
        intExpr: (KExpr, KExpr) -> KExpr<*>,
        realExpr: (KExpr, KExpr) -> KExpr<*>
    ): KExpr<*> = when (val expectedSort = mergeSorts(lhs.sort, rhs.sort)) {
        intSort -> intExpr(lhs.ensureSort(intSort), rhs.ensureSort(intSort))
        realSort -> realExpr(lhs.ensureSort(realSort), rhs.ensureSort(realSort))
        else -> error("Unexpected arith sort ${expectedSort}")
    }

    private fun mergeSorts(lhs: KSort, rhs: KSort): KSort {
        if (lhs == rhs) return lhs

        if (lhs is KArithSort && rhs is KArithSort) {
            return when {
                lhs is KRealSort && rhs is KIntSort -> lhs
                lhs is KIntSort && rhs is KRealSort -> rhs
                else -> error("Can't merge arith sorts $lhs and $rhs")
            }
        }

        if (lhs is KArraySortBase<*> && rhs is KArraySortBase<*>) {
            check(lhs.domainSorts.size == rhs.domainSorts.size) {
                "Can't merge arrays $lhs and $rhs"
            }

            val mergedDomain = lhs.domainSorts.zip(rhs.domainSorts) { l, r -> mergeSorts(l, r) }
            val mergedRange = mergeSorts(lhs.range, rhs.range)

            return ctx.mkAnyArraySort(mergedDomain, mergedRange)
        }

        error("Unexpected sorts merge: $lhs and $rhs")
    }

    private fun castToInt(expr: KExpr): KExpr = ctx.mkRealToInt(expr)
    private fun castToReal(expr: KExpr): KExpr = ctx.mkIntToReal(expr)

    private fun castArray(
        expr: KExpr>,
        sort: KArraySortBase<*>
    ): KExpr> = with(ctx) {
        val expectedDomain = sort.domainSorts
        val actualDomain = expr.sort.domainSorts

        val expectedIndices = expectedDomain.map { mkFreshConst("i", it) }
        val actualIndices = expectedIndices.zip(actualDomain) { idx, actualSort ->
            idx.ensureSort(actualSort)
        }

        val actualBody = mkAnyArraySelectUnchecked(expr, actualIndices)
        val expectedBody = actualBody.ensureSort(sort.range)

        mkAnyArrayLambda(expectedIndices.map { it.decl }, expectedBody)
    }

    private fun  KExpr<*>.ensureSort(sort: S): KExpr {
        val exprSort = this.sort
        return when {
            sort == ctx.boolSort && this is BvBitExtractExpr -> eliminateBitExtract().uncheckedCast()
            exprSort == sort -> this.uncheckedCast()
            exprSort is KIntSort && sort is KRealSort -> castToReal(this.uncheckedCast()).uncheckedCast()
            exprSort is KRealSort && sort is KIntSort -> castToInt(this.uncheckedCast()).uncheckedCast()
            exprSort is KArraySortBase<*> && sort is KArraySortBase<*> -> {
                castArray(this.uncheckedCast(), sort).uncheckedCast()
            }

            else -> error("Unexpected cast from ${this.sort} to $sort")
        }
    }

    private fun > KContext.mkAnyArrayStore(
        array: KExpr,
        indices: List>,
        value: KExpr
    ): KExpr {
        val expectedValueSort = mergeSorts(array.sort.range, value.sort)
        val valueWithCorrectSort = value.ensureSort(expectedValueSort)
        return mkAnyArrayOperation(
            array, expectedValueSort, indices,
            { a, d0 -> mkArrayStore(a, d0, valueWithCorrectSort) },
            { a, d0, d1 -> mkArrayStore(a, d0, d1, valueWithCorrectSort) },
            { a, d0, d1, d2 -> mkArrayStore(a, d0, d1, d2, valueWithCorrectSort) },
            { a, domain -> mkArrayNStore(a, domain, valueWithCorrectSort) }
        ).uncheckedCast()
    }

    private fun > KContext.mkAnyArraySelect(
        array: KExpr,
        indices: List>
    ): KExpr = mkAnyArrayOperation(
        array, array.sort.range, indices,
        { a, d0 -> mkArraySelect(a, d0) },
        { a, d0, d1 -> mkArraySelect(a, d0, d1) },
        { a, d0, d1, d2 -> mkArraySelect(a, d0, d1, d2) },
        { a, domain -> mkArrayNSelect(a, domain) }
    )

    private fun > KContext.mkAnyArraySelectUnchecked(
        array: KExpr,
        indices: List>
    ): KExpr = mkAnyArrayOperation(
        indices,
        { d0 -> mkArraySelect(array.uncheckedCast(), d0) },
        { d0, d1 -> mkArraySelect(array.uncheckedCast(), d0, d1) },
        { d0, d1, d2 -> mkArraySelect(array.uncheckedCast(), d0, d1, d2) },
        { domain -> mkArrayNSelect(array.uncheckedCast(), domain) }
    )

    @Suppress("LongParameterList")
    private inline fun , R> KContext.mkAnyArrayOperation(
        array: KExpr,
        expectedArrayRange: KSort,
        indices: List>,
        array1: (KExpr>, KExpr) -> R,
        array2: (KExpr>, KExpr, KExpr) -> R,
        array3: (KExpr>, KExpr, KExpr, KExpr) -> R,
        arrayN: (KExpr>, List>) -> R
    ): R {
        val expectedIndicesSorts = array.sort.domainSorts.zip(indices) { domainSort, index ->
            mergeSorts(domainSort, index.sort)
        }
        val expectedArraySort = mkAnyArraySort(expectedIndicesSorts, expectedArrayRange)

        val arrayWithCorrectSort = array.ensureSort(expectedArraySort)
        val indicesWithCorrectSorts = indices.zip(expectedIndicesSorts) { index, expectedSort ->
            index.ensureSort(expectedSort)
        }

        return mkAnyArrayOperation(
            indicesWithCorrectSorts,
            { d0 -> array1(arrayWithCorrectSort.uncheckedCast(), d0) },
            { d0, d1 -> array2(arrayWithCorrectSort.uncheckedCast(), d0, d1) },
            { d0, d1, d2 -> array3(arrayWithCorrectSort.uncheckedCast(), d0, d1, d2) },
            { arrayN(arrayWithCorrectSort.uncheckedCast(), it) }
        )
    }

    private fun KContext.mkAnyArrayLambda(domain: List>, body: KExpr<*>) =
        mkAnyArrayOperation(
            domain,
            { d0 -> mkArrayLambda(d0, body) },
            { d0, d1 -> mkArrayLambda(d0, d1, body) },
            { d0, d1, d2 -> mkArrayLambda(d0, d1, d2, body) },
            { mkArrayNLambda(it, body) }
        )

    private fun KContext.mkAnyArraySort(domain: List, range: KSort): KArraySortBase =
        mkAnyArrayOperation(
            domain,
            { d0 -> mkArraySort(d0, range) },
            { d0, d1 -> mkArraySort(d0, d1, range) },
            { d0, d1, d2 -> mkArraySort(d0, d1, d2, range) },
            { mkArrayNSort(it, range) }
        )

    private inline fun  mkAnyArrayOperation(
        domain: List,
        array1: (T) -> R,
        array2: (T, T) -> R,
        array3: (T, T, T) -> R,
        arrayN: (List) -> R
    ): R = when (domain.size) {
        KArraySort.DOMAIN_SIZE -> array1(domain.single())
        KArray2Sort.DOMAIN_SIZE -> array2(domain.first(), domain.last())
        KArray3Sort.DOMAIN_SIZE -> {
            val (d0, d1, d2) = domain
            array3(d0, d1, d2)
        }

        else -> arrayN(domain)
    }

    /**
     * Yices use bit-blasting for bv logical expressions resulting in an enormous
     * amount of expressions.
     *
     * Usually the following pattern occurred:
     * 1. Single bit extraction operation (bv1 -> bool)
     * 2. Boolean operations with the extracted bits
     * 3. BvArray operation that concatenates individual bits to a single Bv
     *
     * We delay bit extraction operations until we definitely know it usage context:
     * 1. Bit is used as a boolean expression. We eliminate all lazy bit-level operations
     * and replace them with normal boolean operations.
     * 2. Bit us used in a BvArray expression. We can try to merge bits into a single Bv
     * and apply normal Bv operations (e.g. BvAnd).
     * */
    private sealed class BvBitExtractExpr(ctx: KContext) : KExpr(ctx) {
        override val sort: KBoolSort = ctx.boolSort

        abstract fun canBeJoinedWith(other: BvBitExtractExpr): Boolean

        override fun print(printer: ExpressionPrinter) {
            printer.append("(bit extract)")
        }

        override fun accept(transformer: KTransformerBase): KExpr {
            transformer as? BvBitExtractTransformer ?: error("Leaked bit extract aux expr")
            return accept(transformer)
        }

        abstract fun accept(transformer: BvBitExtractTransformer): KExpr

        override fun internEquals(other: Any): Boolean = error("Interning is not used for bv bit extract")
        override fun internHashCode(): Int = error("Interning is not used for bv bit extract")
    }

    private interface BvBitExtractTransformer : KTransformerBase {
        fun transform(expr: BvBitExtractRoot): KExpr

        fun transform(expr: BvBitExtractEq): KExpr
        fun transform(expr: BvBitExtractIte): KExpr
        fun transform(expr: BvBitExtractDistinct): KExpr

        fun transform(expr: BvBitExtractNot): KExpr
        fun transform(expr: BvBitExtractOr): KExpr
        fun transform(expr: BvBitExtractXor): KExpr
    }

    private inner class BvBitExtractEliminator : KNonRecursiveTransformer(ctx), BvBitExtractTransformer {
        override fun transform(expr: BvBitExtractRoot): KExpr = with(ctx) {
            val bv1 = mkBvExtractExpr(expr.idx, expr.idx, expr.expr)
            val condition = mkEq(mkBv(true, 1u), bv1)
            mkIte(condition, mkTrue(), mkFalse())
        }

        override fun transform(expr: BvBitExtractEq): KExpr =
            transformExprAfterTransformed(expr, expr.lhs, expr.rhs) { lhs, rhs ->
                converterMkEq(lhs.uncheckedCast(), rhs.uncheckedCast())
            }

        override fun transform(expr: BvBitExtractIte): KExpr =
            transformExprAfterTransformed(expr, expr.trueBranch, expr.falseBranch) { tb, fb ->
                converterMkIte(expr.condition, tb.uncheckedCast(), fb.uncheckedCast()).uncheckedCast()
            }

        override fun transform(expr: BvBitExtractDistinct): KExpr =
            transformExprAfterTransformed(expr, expr.args) { args ->
                converterMkDistinct(args.uncheckedCast())
            }

        override fun transform(expr: BvBitExtractNot): KExpr =
            transformExprAfterTransformed(expr, expr.arg) { arg ->
                converterMkNot(arg)
            }

        override fun transform(expr: BvBitExtractOr): KExpr =
            transformExprAfterTransformed(expr, expr.args) { args ->
                converterMkOr(args.uncheckedCast())
            }

        override fun transform(expr: BvBitExtractXor): KExpr =
            transformExprAfterTransformed(expr, expr.args) { args ->
                converterMkXor(args.uncheckedCast())
            }
    }

    private fun KExpr<*>.eliminateBitExtract(): KExpr<*> {
        if (this !is BvBitExtractExpr) return this
        return BvBitExtractEliminator().apply(this)
    }

    private inner class BvBitExtractRoot(
        val expr: KExpr,
        val idx: Int
    ) : BvBitExtractExpr(ctx) {
        override fun canBeJoinedWith(other: BvBitExtractExpr): Boolean =
            other is BvBitExtractRoot && other.expr == expr

        override fun accept(transformer: BvBitExtractTransformer): KExpr =
            transformer.transform(this)
    }

    private inner class BvBitExtractEq(
        val lhs: KExpr,
        val rhs: KExpr
    ) : BvBitExtractExpr(ctx) {
        override fun canBeJoinedWith(other: BvBitExtractExpr): Boolean =
            other is BvBitExtractEq

        override fun accept(transformer: BvBitExtractTransformer): KExpr =
            transformer.transform(this)
    }

    private inner class BvBitExtractIte(
        val condition: KExpr,
        val trueBranch: KExpr,
        val falseBranch: KExpr
    ) : BvBitExtractExpr(ctx) {
        override fun canBeJoinedWith(other: BvBitExtractExpr): Boolean =
            other is BvBitExtractIte && other.condition == condition

        override fun accept(transformer: BvBitExtractTransformer): KExpr =
            transformer.transform(this)
    }

    private inner class BvBitExtractDistinct(
        val args: List>
    ) : BvBitExtractExpr(ctx) {
        override fun canBeJoinedWith(other: BvBitExtractExpr): Boolean =
            other is BvBitExtractDistinct && args.size == other.args.size

        override fun accept(transformer: BvBitExtractTransformer): KExpr =
            transformer.transform(this)
    }

    private inner class BvBitExtractNot(
        val arg: KExpr
    ) : BvBitExtractExpr(ctx) {
        override fun canBeJoinedWith(other: BvBitExtractExpr): Boolean =
            other is BvBitExtractNot

        override fun accept(transformer: BvBitExtractTransformer): KExpr =
            transformer.transform(this)
    }

    private inner class BvBitExtractOr(
        val args: List>
    ) : BvBitExtractExpr(ctx) {
        override fun canBeJoinedWith(other: BvBitExtractExpr): Boolean =
            other is BvBitExtractOr && args.size == other.args.size

        override fun accept(transformer: BvBitExtractTransformer): KExpr =
            transformer.transform(this)
    }

    private inner class BvBitExtractXor(
        val args: List>
    ) : BvBitExtractExpr(ctx) {
        override fun canBeJoinedWith(other: BvBitExtractExpr): Boolean =
            other is BvBitExtractXor && args.size == other.args.size

        override fun accept(transformer: BvBitExtractTransformer): KExpr =
            transformer.transform(this)
    }

    private sealed interface BvArrayChunk {
        fun isEmpty(): Boolean
        fun add(expr: KExpr): BvArrayChunk
        fun process(): KExpr
    }

    private inner class BitExtractChunk(
        val chunk: MutableList
    ) : BvArrayChunk {
        override fun isEmpty(): Boolean = chunk.isEmpty()

        override fun add(expr: KExpr): BvArrayChunk = when {
            expr !is BvBitExtractExpr -> OtherChunk(mutableListOf(expr))
            !chunk.last().canBeJoinedWith(expr) -> BitExtractChunk(mutableListOf(expr))
            else -> this.also { chunk.add(expr.uncheckedCast()) }
        }

        override fun process(): KExpr = applyBitExtractOperation(ctx, chunk.first(), chunk)
    }

    private inner class OtherChunk(val chunk: MutableList>) : BvArrayChunk {
        override fun isEmpty(): Boolean = chunk.isEmpty()
        override fun add(expr: KExpr): BvArrayChunk = if (expr is BvBitExtractExpr) {
            BitExtractChunk(mutableListOf(expr))
        } else {
            this.also { chunk += expr }
        }

        override fun process(): KExpr = with(ctx) {
            val bvArgs: List> = chunk.map { element: KExpr ->
                mkIte(element, mkBv(true), mkBv(false))
            }.uncheckedCast()

            bvArgs.reduceConcat()
        }
    }

    private fun List>.reduceConcat(): KExpr = with(ctx){
        reduce(::mkBvConcatExpr)
    }

    private fun findBvArrayChunks(args: List>): List {
        var chunk: BvArrayChunk = OtherChunk(mutableListOf())
        val chunks = mutableListOf(chunk)

        for (arg in args) {
            val newChunk = chunk.add(arg)
            if (newChunk !== chunk) {
                chunk = newChunk
                chunks += newChunk
            }
        }

        return chunks.filterNot { it.isEmpty() }
    }

    private fun processBvArrayChunked(args: List>): KExpr {
        val unprocessedChunks = findBvArrayChunks(args)
        val chunksExpressions = unprocessedChunks.map { it.process() }
        return chunksExpressions.reduceConcat()
    }

    private inner class BitExtractApplyOperation(
        override val ctx: KContext,
        val chunk: List
    ) : BvBitExtractTransformer, KTransformer {
        lateinit var result: KExpr

        override fun transform(expr: BvBitExtractRoot): KExpr {
            result = processRoot(chunk.uncheckedCast())
            return expr
        }

        override fun transform(expr: BvBitExtractEq): KExpr {
            result = processEq(chunk.uncheckedCast())
            return expr
        }

        override fun transform(expr: BvBitExtractIte): KExpr {
            result = processIte(chunk.uncheckedCast())
            return expr
        }

        override fun transform(expr: BvBitExtractDistinct): KExpr {
            result = processDistinct(chunk.uncheckedCast())
            return expr
        }

        override fun transform(expr: BvBitExtractNot): KExpr {
            result = processNot(chunk.uncheckedCast())
            return expr
        }

        override fun transform(expr: BvBitExtractOr): KExpr {
            result = processOr(chunk.uncheckedCast())
            return expr
        }

        override fun transform(expr: BvBitExtractXor): KExpr {
            result = processXor(chunk.uncheckedCast())
            return expr
        }

        private fun processRoot(chunk: List): KExpr = with(ctx) {
            val groupedExtracts = groupBySubsequentIndices(chunk)
            val extracts = groupedExtracts.map { mkBvExtractExpr(it.first().idx, it.last().idx, chunk.first().expr) }
            extracts.reduceConcat()
        }

        private fun groupBySubsequentIndices(chunk: List): List> {
            var currentGroup = mutableListOf(chunk.first())
            val result = mutableListOf(currentGroup)

            for (i in 1 until chunk.size) {
                val element = chunk[i]
                if (element.idx == currentGroup.last().idx - 1) {
                    currentGroup += element
                    continue
                }
                currentGroup = mutableListOf(element)
                result += currentGroup
            }

            return result
        }

        private fun processEq(chunk: List): KExpr = with(ctx) {
            val lhsExpr = processBvArrayChunked(chunk.map { it.lhs })
            val rhsExpr = processBvArrayChunked(chunk.map { it.rhs })

            mkBvNotExpr(mkBvXorExpr(lhsExpr, rhsExpr))
        }

        private fun processDistinct(chunk: List): KExpr = with(ctx) {
            val args = processChunkList(chunk.map { it.args })
            when (args.size) {
                1 -> bvMaxValueUnsigned(args.single().sort.sizeBits)
                2 -> mkBvXorExpr(args.first(), args.last())
                else -> bvZero(args.single().sort.sizeBits)
            }.uncheckedCast()
        }

        private fun processIte(chunk: List): KExpr = with(ctx) {
            val trueExpr = processBvArrayChunked(chunk.map { it.trueBranch })
            val falseExpr = processBvArrayChunked(chunk.map { it.falseBranch })

            mkIte(chunk.first().condition, trueExpr, falseExpr)
        }

        private fun processNot(chunk: List): KExpr = with(ctx) {
            val arg = processBvArrayChunked(chunk.map { it.arg })
            mkBvNotExpr(arg)
        }

        private fun processOr(chunk: List): KExpr = with(ctx) {
            val args = processChunkList(chunk.map { it.args })
            args.reduce { acc, expr -> mkBvOrExpr(acc, expr) }
        }

        private fun processXor(chunk: List): KExpr = with(ctx) {
            val args = processChunkList(chunk.map { it.args })
            args.reduce { acc, expr -> mkBvXorExpr(acc, expr) }
        }

        private fun processChunkList(chunkArgs: List>>): List> {
            val result = mutableListOf>()
            for (i in chunkArgs.first().indices) {
                result += processBvArrayChunked(chunkArgs.map { it[i] })
            }
            return result
        }
    }

    private fun  applyBitExtractOperation(
        ctx: KContext,
        prototype: T,
        chunk: List
    ): KExpr {
        val operationApplier = BitExtractApplyOperation(ctx, chunk)
        prototype.accept(operationApplier)
        return operationApplier.result
    }
}