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

commonMain.com.sunnychung.lib.multiplatform.kotlite.Interpreter.kt Maven / Gradle / Ivy

Go to download

A Kotlin Multiplatform library to interpret Kotlite code, which is a subset of Kotlin language, in runtime in a safe way.

The newest version!
package com.sunnychung.lib.multiplatform.kotlite

import com.sunnychung.lib.multiplatform.kotlite.error.EvaluateNullPointerException
import com.sunnychung.lib.multiplatform.kotlite.error.EvaluateRuntimeException
import com.sunnychung.lib.multiplatform.kotlite.error.EvaluateTypeCastException
import com.sunnychung.lib.multiplatform.kotlite.error.IdentifierClassifier
import com.sunnychung.lib.multiplatform.kotlite.error.controlflow.NormalBreakException
import com.sunnychung.lib.multiplatform.kotlite.error.controlflow.NormalContinueException
import com.sunnychung.lib.multiplatform.kotlite.error.controlflow.NormalReturnException
import com.sunnychung.lib.multiplatform.kotlite.extension.emptyToNull
import com.sunnychung.lib.multiplatform.kotlite.extension.fullClassName
import com.sunnychung.lib.multiplatform.kotlite.extension.isValidIntegerLiteralAssignToByte
import com.sunnychung.lib.multiplatform.kotlite.extension.resolveGenericParameterTypeArguments
import com.sunnychung.lib.multiplatform.kotlite.extension.resolveGenericParameterTypeToUpperBound
import com.sunnychung.lib.multiplatform.kotlite.model.ASTNode
import com.sunnychung.lib.multiplatform.kotlite.model.AsOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.AssignmentNode
import com.sunnychung.lib.multiplatform.kotlite.model.BinaryOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.BlockNode
import com.sunnychung.lib.multiplatform.kotlite.model.BooleanNode
import com.sunnychung.lib.multiplatform.kotlite.model.BooleanValue
import com.sunnychung.lib.multiplatform.kotlite.model.BreakNode
import com.sunnychung.lib.multiplatform.kotlite.model.ByteValue
import com.sunnychung.lib.multiplatform.kotlite.model.CallStack
import com.sunnychung.lib.multiplatform.kotlite.model.CallableNode
import com.sunnychung.lib.multiplatform.kotlite.model.CallableType
import com.sunnychung.lib.multiplatform.kotlite.model.CatchNode
import com.sunnychung.lib.multiplatform.kotlite.model.CharNode
import com.sunnychung.lib.multiplatform.kotlite.model.CharValue
import com.sunnychung.lib.multiplatform.kotlite.model.ClassDeclarationNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassDefinition
import com.sunnychung.lib.multiplatform.kotlite.model.ClassInstance
import com.sunnychung.lib.multiplatform.kotlite.model.ClassInstanceInitializerNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassMemberReferenceNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassModifier
import com.sunnychung.lib.multiplatform.kotlite.model.ClassParameterNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassPrimaryConstructorNode
import com.sunnychung.lib.multiplatform.kotlite.model.ClassTypeNode
import com.sunnychung.lib.multiplatform.kotlite.model.ComparableRuntimeValue
import com.sunnychung.lib.multiplatform.kotlite.model.ContinueNode
import com.sunnychung.lib.multiplatform.kotlite.model.CustomFunctionDeclarationNode
import com.sunnychung.lib.multiplatform.kotlite.model.CustomFunctionDefinition
import com.sunnychung.lib.multiplatform.kotlite.model.CustomFunctionParameter
import com.sunnychung.lib.multiplatform.kotlite.model.DataType
import com.sunnychung.lib.multiplatform.kotlite.model.DoWhileNode
import com.sunnychung.lib.multiplatform.kotlite.model.DoubleNode
import com.sunnychung.lib.multiplatform.kotlite.model.DoubleValue
import com.sunnychung.lib.multiplatform.kotlite.model.ElvisOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.EnumEntryNode
import com.sunnychung.lib.multiplatform.kotlite.model.ExceptionValue
import com.sunnychung.lib.multiplatform.kotlite.model.ExecutionEnvironment
import com.sunnychung.lib.multiplatform.kotlite.model.ExtensionProperty
import com.sunnychung.lib.multiplatform.kotlite.model.ForNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionCallArgumentNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionCallNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionCallResult
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionDeclarationNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionType
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionTypeNode
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionValueParameterModifier
import com.sunnychung.lib.multiplatform.kotlite.model.FunctionValueParameterNode
import com.sunnychung.lib.multiplatform.kotlite.model.IfNode
import com.sunnychung.lib.multiplatform.kotlite.model.IndexOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.InfixFunctionCallNode
import com.sunnychung.lib.multiplatform.kotlite.model.IntValue
import com.sunnychung.lib.multiplatform.kotlite.model.IntegerNode
import com.sunnychung.lib.multiplatform.kotlite.model.LabelNode
import com.sunnychung.lib.multiplatform.kotlite.model.LambdaLiteralNode
import com.sunnychung.lib.multiplatform.kotlite.model.LambdaValue
import com.sunnychung.lib.multiplatform.kotlite.model.ListValue
import com.sunnychung.lib.multiplatform.kotlite.model.LongNode
import com.sunnychung.lib.multiplatform.kotlite.model.LongValue
import com.sunnychung.lib.multiplatform.kotlite.model.NavigationNode
import com.sunnychung.lib.multiplatform.kotlite.model.NullNode
import com.sunnychung.lib.multiplatform.kotlite.model.NullValue
import com.sunnychung.lib.multiplatform.kotlite.model.NumberValue
import com.sunnychung.lib.multiplatform.kotlite.model.ObjectType
import com.sunnychung.lib.multiplatform.kotlite.model.PairValue
import com.sunnychung.lib.multiplatform.kotlite.model.PrimitiveTypeName
import com.sunnychung.lib.multiplatform.kotlite.model.PrimitiveValue
import com.sunnychung.lib.multiplatform.kotlite.model.PropertyAccessorsNode
import com.sunnychung.lib.multiplatform.kotlite.model.PropertyDeclarationNode
import com.sunnychung.lib.multiplatform.kotlite.model.ReturnNode
import com.sunnychung.lib.multiplatform.kotlite.model.RuntimeValue
import com.sunnychung.lib.multiplatform.kotlite.model.ScopeType
import com.sunnychung.lib.multiplatform.kotlite.model.ScriptNode
import com.sunnychung.lib.multiplatform.kotlite.model.SourcePosition
import com.sunnychung.lib.multiplatform.kotlite.model.SpecialFunction
import com.sunnychung.lib.multiplatform.kotlite.model.StringLiteralNode
import com.sunnychung.lib.multiplatform.kotlite.model.StringNode
import com.sunnychung.lib.multiplatform.kotlite.model.StringValue
import com.sunnychung.lib.multiplatform.kotlite.model.SymbolTable
import com.sunnychung.lib.multiplatform.kotlite.model.ThrowNode
import com.sunnychung.lib.multiplatform.kotlite.model.ThrowableValue
import com.sunnychung.lib.multiplatform.kotlite.model.TryNode
import com.sunnychung.lib.multiplatform.kotlite.model.TypeNode
import com.sunnychung.lib.multiplatform.kotlite.model.TypeParameterNode
import com.sunnychung.lib.multiplatform.kotlite.model.UnaryOpNode
import com.sunnychung.lib.multiplatform.kotlite.model.UnitType
import com.sunnychung.lib.multiplatform.kotlite.model.UnitValue
import com.sunnychung.lib.multiplatform.kotlite.model.ValueNode
import com.sunnychung.lib.multiplatform.kotlite.model.ValueParameterDeclarationNode
import com.sunnychung.lib.multiplatform.kotlite.model.VariableReferenceNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhenConditionNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhenEntryNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhenNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhenSubjectNode
import com.sunnychung.lib.multiplatform.kotlite.model.WhileNode
import com.sunnychung.lib.multiplatform.kotlite.util.ClassMemberResolver

open class Interpreter(val rootNode: ASTNode, val executionEnvironment: ExecutionEnvironment) {

    internal val callStack = CallStack()
    internal val globalScope = callStack.currentSymbolTable()

    init {
        val classes = mutableListOf()
        executionEnvironment.getBuiltinClasses(globalScope).forEach {
            it.attachToInterpreter(this) // make "ObjectType" resolvable
            callStack.provideBuiltinClass(it)
            classes += it
        }
        callStack.builtinScope().init()
        executionEnvironment.getGlobalProperties(globalScope).forEach {
            it.attachToInterpreter(this)
            globalScope.putPropertyHolder(it.transformedName!!, it.isMutable, it.accessor)
        }
        executionEnvironment.getBuiltinFunctions(globalScope).forEach {
            callStack.provideBuiltinFunction(it)
        }
        executionEnvironment.getExtensionProperties(globalScope).forEach {
            callStack.provideBuiltinExtensionProperty(it)
        }
        globalScope.init()
        classes.forEach { // do this again after registering functions again to make the post-resolution logic works
            it.attachToInterpreter(this)
        }
    }

    fun symbolTable() = callStack.currentSymbolTable()

    fun ASTNode.eval(): Any {
        return when (this) {
            is AssignmentNode -> this.eval()
            is BinaryOpNode -> this.eval()
            is IntegerNode -> this.eval()
            is LongNode -> this.eval()
            is DoubleNode -> this.eval()
            is BooleanNode -> this.eval()
            is NullNode -> this.eval()
            is PropertyDeclarationNode -> this.eval()
            is ScriptNode -> this.eval()
            is TypeNode -> throw UnsupportedOperationException()
            is UnaryOpNode -> this.eval()
            is VariableReferenceNode -> this.eval()
            is FunctionDeclarationNode -> this.eval()
            is FunctionValueParameterNode -> throw UnsupportedOperationException()
            is FunctionCallArgumentNode -> throw UnsupportedOperationException()
            is FunctionCallNode -> this.eval()
            is BlockNode -> this.eval()
            is ReturnNode -> this.eval()
            is IfNode -> this.eval()
            is WhileNode -> this.eval()
            is DoWhileNode -> this.eval()
            is BreakNode -> this.eval()
            is ContinueNode -> this.eval()
            is ClassDeclarationNode -> this.eval()
            is ClassInstanceInitializerNode -> TODO()
            is ClassParameterNode -> TODO()
            is ClassPrimaryConstructorNode -> TODO()
            is ClassMemberReferenceNode -> TODO()
            is NavigationNode -> this.eval()
            is PropertyAccessorsNode -> TODO()
            is ValueNode -> this.eval()
            is StringLiteralNode -> this.eval()
            is StringNode -> this.eval()
            is LambdaLiteralNode -> this.eval()
            is CharNode -> this.eval()
            is AsOpNode -> this.eval()
            is TypeParameterNode -> TODO()
            is IndexOpNode -> this.eval()
            is InfixFunctionCallNode -> this.eval()
            is ElvisOpNode -> this.eval()
            is ThrowNode -> this.eval()
            is CatchNode -> TODO()
            is TryNode -> this.eval()
            is WhenConditionNode -> TODO()
            is WhenEntryNode -> TODO()
            is WhenNode -> this.eval()
            is WhenSubjectNode -> TODO()
            is LabelNode -> TODO()
            is EnumEntryNode -> TODO()
            is ForNode -> this.eval()
            is ValueParameterDeclarationNode -> TODO()
        }
    }

    fun ScriptNode.eval() {
        nodes.forEach { it.eval() }
    }

    fun  castType(a: Any, calculation: (T) -> R): R
        = calculation(a as T)

    fun  castType(a: Any, b: Any, calculation: (T, T) -> R): R
        = calculation(a as T, b as T)

    fun BinaryOpNode.eval(): RuntimeValue {
        if (hasFunctionCall == true) {
            val result = call!!.eval()
            return when (operator) {
                ">" -> BooleanValue((result as IntValue).value > 0)
                ">=" -> BooleanValue((result as IntValue).value >= 0)
                "<" -> BooleanValue((result as IntValue).value < 0)
                "<=" -> BooleanValue((result as IntValue).value <= 0)
                else -> result
            }
        }

        return when (operator) { // TODO overflow
            "+" -> {
                val r1 = node1.eval() as RuntimeValue
                val r2 = node2.eval() as RuntimeValue
                if (r1 is StringValue || r1 is NullValue || r2 is StringValue || r2 is NullValue) {
                    StringValue(r1.convertToString() + r2.convertToString())
                } else if (r1 is CharValue && r2 is IntValue) {
                    CharValue(r1.value + r2.value)
                } else {
                    castType, NumberValue<*>>(r1, r2) { a, b -> a + b }
                }
            }
            "-" -> {
                val r1 = node1.eval() as RuntimeValue
                val r2 = node2.eval() as RuntimeValue
                if (r1 is CharValue && r2 is CharValue) {
                    IntValue(r1.value - r2.value)
                } else {
                    castType, NumberValue<*>>(r1, r2) { a, b -> a - b }
                }
            }
            "*" -> castType, NumberValue<*>>(node1.eval(), node2.eval()) { a, b -> a * b }
            "/" -> castType, NumberValue<*>>(node1.eval(), node2.eval()) { a, b -> a / b }
            "%" -> castType, NumberValue<*>>(node1.eval(), node2.eval()) { a, b -> a % b }

            "<" -> {
//                val r1 = node1.eval() as RuntimeValue
//                val r2 = node2.eval() as RuntimeValue
//                val r1 = node1.eval() as ComparableRuntimeValue>>
//                val r2 = node2.eval() as ComparableRuntimeValue>>
//                BooleanValue(r1 < r2)
                BooleanValue(node1.eval() as ComparableRuntimeValue>, Comparable>> < node2.eval() as ComparableRuntimeValue>, Comparable>>)
//                if (r1 is )
//                castType, BooleanValue>(node1.eval(), node2.eval()) { a, b -> BooleanValue(a < b) }
            }
            "<=" -> BooleanValue(node1.eval() as ComparableRuntimeValue>, Comparable>> <= node2.eval() as ComparableRuntimeValue>, Comparable>>)
            ">" -> BooleanValue(node1.eval() as ComparableRuntimeValue>, Comparable>> > node2.eval() as ComparableRuntimeValue>, Comparable>>)
            ">=" -> BooleanValue(node1.eval() as ComparableRuntimeValue>, Comparable>> >= node2.eval() as ComparableRuntimeValue>, Comparable>>)
            "==" -> {
                val r1 = node1.eval() as RuntimeValue
                val r2 = node2.eval() as RuntimeValue
//                if (r1 is NullValue || r2 is NullValue) {
//                    return BooleanValue(r1 == r2)
//                }
                if (r1 is NumberValue<*> && r2 is NumberValue<*>) {
                    return castType, BooleanValue>(r1, r2) { a, b -> BooleanValue(a == b) }
                }
                if (r1 is ClassInstance) {
                    r1.clazz!!.getSpecialFunction(SpecialFunction.Name.Equals)?.let { func ->
                        log.d { "equals($r1, $r2)" }
                        return func.call(interpreter = this@Interpreter, subject = r1, arguments = listOf(r2))
                    }
                }
                return BooleanValue(r1 == r2)
            }
            "!=" -> {
                val r1 = node1.eval() as RuntimeValue
                val r2 = node2.eval() as RuntimeValue
//                if (r1 is NullValue || r2 is NullValue) {
//                    return BooleanValue(r1 != r2)
//                }
                if (r1 is NumberValue<*> && r2 is NumberValue<*>) {
                    return castType, BooleanValue>(r1, r2) { a, b -> BooleanValue(a != b) }
                }
                if (r1 is ClassInstance) {
                    r1.clazz!!.getSpecialFunction(SpecialFunction.Name.Equals)?.let { func ->
                        log.d { "!equals($r1, $r2)" }
                        return (func.call(interpreter = this@Interpreter, subject = r1, arguments = listOf(r2)) as BooleanValue)
                            .let { BooleanValue(!it.value) }
                    }
                }
                return BooleanValue(r1 != r2)
            }

            "||" -> castType(node1.eval()) { a -> if (a.value) BooleanValue(true) else node2.eval() as BooleanValue }
            "&&" -> castType(node1.eval()) { a -> if (!a.value) BooleanValue(false) else node2.eval() as BooleanValue }

            else -> throw UnsupportedOperationException()
        }
    }

    fun UnaryOpNode.eval(): RuntimeValue {
        val result = node!!.eval()
        if (operator == "!!") {
            if (result === NullValue) {
                throw EvaluateNullPointerException(callStack.currentSymbolTable(), callStack.getStacktrace(position))
            }
            return result as RuntimeValue
        }
        return when (result) {
            is NumberValue<*> -> when (operator) {
                "+" -> IntValue(0) + result
                "-" -> IntValue(0) - result
                "pre++" -> {
                    val newValue = result + IntValue(1)
                    node!!.write(newValue)
                    newValue
                }
                "pre--" -> {
                    val newValue = result - IntValue(1)
                    node!!.write(newValue)
                    newValue
                }
                "post++" -> {
                    val newValue = result + IntValue(1)
                    node!!.write(newValue)
                    result
                }
                "post--" -> {
                    val newValue = result - IntValue(1)
                    node!!.write(newValue)
                    result
                }
                else -> throw UnsupportedOperationException()
            }

            is BooleanValue -> {
                when (operator) {
                    "!" -> BooleanValue(!result.value)
                    else -> throw UnsupportedOperationException()
                }
            }
            else -> TODO()
        }
    }

    fun PropertyDeclarationNode.eval() {
        val symbolTable = callStack.currentSymbolTable()
        val name = transformedRefName!!
        if (initialValue != null) {
            var value = initialValue.eval()
            symbolTable.declareProperty(position, name, type, isMutable)
            if (isValidIntegerLiteralAssignToByte(initialValue, symbolTable().assertToDataType(type))) {
                value = ByteValue((value as IntValue).value.toByte(), symbolTable())
            }
            symbolTable.assign(name, value as RuntimeValue)
        } else {
            symbolTable.declareProperty(position, name, type, isMutable)
        }
    }

    protected fun ASTNode.write(value: RuntimeValue) {
        when (this) {
            is VariableReferenceNode -> {
                if (this.ownerRef != null) {
                    NavigationNode(
                        position = position,
                        subject = VariableReferenceNode(position = position, variableName = ownerRef!!.ownerRefName),
                        operator = ".",
                        member = ClassMemberReferenceNode(
                            position = this.position,
                            name = this.variableName,
                            transformedRefName = this.transformedRefName
                        ),
                        memberType = NavigationNode.MemberType.Extension,
                        transformedRefName = ownerRef!!.extensionPropertyRef
                    ).write(value)
                } else {
                    callStack.currentSymbolTable().assign(this.transformedRefName ?: this.variableName, value)
                }
            }

            is NavigationNode -> {
                val subject = (this.subject.eval() as RuntimeValue)
                    .let { resolveSuperKeyword(it) }

                if (transformedRefName != null) { // extension property
                    val extensionProperty = symbolTable().findExtensionProperty(transformedRefName!!) ?: throw RuntimeException("Extension property `$transformedRefName` not found")
                    val typeArgumentsMap = extensionProperty.typeArgumentsMap(subject.type())
                    (extensionProperty.setter ?: throw RuntimeException("Setter not found"))(
                        this@Interpreter,
                        subject,
                        value,
                        typeArgumentsMap,
                    )
                    return
                }

                val obj = subject as ClassInstance
//                    obj.assign((subject.member as ClassMemberReferenceNode).transformedRefName!!, value)
                // before type resolution is implemented in SemanticAnalyzer, reflect from clazz as a slower alternative
                obj.assign(interpreter = this@Interpreter, name = obj.clazz!!.findMemberPropertyTransformedName((this.member as ClassMemberReferenceNode).name)!!, value = value)/*?.also {
                    FunctionCallNode(
                        it,
                        listOf(FunctionCallArgumentNode(index = 0, value = ValueNode(value))),
                        SourcePosition(1, 1)
                    ).evalClassMemberAnyFunctionCall(obj, it)
                } // TODO remove */
            }

            else -> throw UnsupportedOperationException()
        }
    }

    fun NavigationNode.resolveSuperKeyword(subjectValue: RuntimeValue): RuntimeValue {
        // a hack to resolve the "super" keyword. See documentation
        return if (subject is VariableReferenceNode && subject.variableName == "super" && (subjectValue as ClassInstance).parentInstance != null) {
            var instance: ClassInstance? = subjectValue as ClassInstance
            val typeOfSuper = subject.type!!
            while (instance != null && instance.clazz!!.fullQualifiedName != typeOfSuper.name) {
                instance = instance.parentInstance
            }
            instance!!
        } else {
            subjectValue
        }
    }

    fun AssignmentNode.eval() {
        if (subject is NavigationNode && subject.operator == "?.") {
            throw UnsupportedOperationException("?: on left side of assignment is not supported")
        }
        val result = value.eval() as RuntimeValue

        wholeFunctionCall?.let { func ->
            func.eval(replaceArguments = mapOf(0 to result))
            return
        }

        val read = { subject.eval() as RuntimeValue }
        val write = { value: RuntimeValue ->
            if (assignFunctionCall != null) {
                // TODO any less "hacky" way to implement?
                assignFunctionCall!!.eval(replaceArguments = mapOf(assignFunctionCall!!.arguments.lastIndex to value))
            } else {
                subject.write(value)
            }
        }

        val finalResult = if (operator == "=") {
            if (subject.declaredType() isPrimitiveTypeOf PrimitiveTypeName.Byte && result !is ByteValue) {
                if (isValidIntegerLiteralAssignToByte(value, subject.declaredType())) {
                    ByteValue((result as IntValue).value.toByte(), symbolTable())
                } else {
                    throw RuntimeException("The integer value cannot be assigned to a Byte due to out of range")
                }
            } else {
                result as RuntimeValue
            }
        } else {
            val existing = read()
            preAssignFunctionCall?.let { func ->
                write(func.eval(replaceArguments = mapOf(0 to result)))
                return
            }
            val newResult = when (operator) {
                "+=" -> {
                    if (subject.declaredType() isPrimitiveTypeOf PrimitiveTypeName.String) {
                        StringValue(existing.convertToString() + result.convertToString())
                    }  else {
                        (existing as NumberValue<*>) + result as NumberValue<*>
                    }
                }
                "-=" -> (existing as NumberValue<*>) - result as NumberValue<*>
                "*=" -> (existing as NumberValue<*>) * result as NumberValue<*>
                "/=" -> (existing as NumberValue<*>) / result as NumberValue<*>
                "%=" -> (existing as NumberValue<*>) % result as NumberValue<*>
                else -> throw UnsupportedOperationException()
            }
            newResult
        }
        write(finalResult)
    }

    fun VariableReferenceNode.eval(): RuntimeValue {
        // usual variable -> transformedRefName
        // class constructor -> variableName? TODO
        if (ownerRef != null) {
            return NavigationNode(
                position = position,
                subject = VariableReferenceNode(position = position, variableName = ownerRef!!.ownerRefName),
                operator = ".",
                member = ClassMemberReferenceNode(
                    position = position,
                    name = variableName,
                    transformedRefName = transformedRefName
                ),
                memberType = NavigationNode.MemberType.Extension,
                transformedRefName = ownerRef!!.extensionPropertyRef,
            ).eval()
        }
        if (type is ClassTypeNode) {
            // TODO return singleton
            val companionClassName = "${(type as ClassTypeNode).clazz.name}.Companion"
            return ClassInstance(
                symbolTable(),
                companionClassName,
                symbolTable().findClass(companionClassName)!!.first,
                emptyList(),
            )
        }
        return callStack.currentSymbolTable().read(transformedRefName ?: variableName)
    }

    fun FunctionDeclarationNode.eval() {
        if (receiver == null) {
            callStack.currentSymbolTable().declareFunction(position, transformedRefName!!, this)
        } else {
            globalScope.declareExtensionFunction(position, transformedRefName!!, this)
        }
    }

    fun FunctionCallNode.eval(replaceArguments: Map = emptyMap()): RuntimeValue {
        // TODO move to semantic analyzer
        when (function) {
            is VariableReferenceNode, is TypeNode -> {
                val directName = when (function) {
                    is VariableReferenceNode -> function.variableName
                    is TypeNode -> function.name
                    else -> throw UnsupportedOperationException()
                }

                if (function is VariableReferenceNode && function.ownerRef != null) {
                    return this.copy(
                        function = NavigationNode(
                            position = position,
                            subject = VariableReferenceNode(
                                position = position,
                                variableName = this.function.ownerRef!!.ownerRefName
                            ),
                            operator = ".",
                            member = ClassMemberReferenceNode(position = position, name = directName),
                            memberType = NavigationNode.MemberType.Extension,
                            transformedRefName = this.function.ownerRef!!.extensionPropertyRef,
                        )
                    ).eval()
                }

                when (callableType) {
                    CallableType.Function -> {
                        val functionNode = callStack.currentSymbolTable().findFunction(functionRefName!!)?.first
                        if (functionNode != null) {
                            return evalFunctionCall(functionNode, replaceArguments = replaceArguments)
                        }
                    }
                    CallableType.Constructor -> {
                        val classDefinition = callStack.currentSymbolTable().findClass(functionRefName!!)?.first
                        if (classDefinition != null) {
                            return evalCreateClassInstance(classDefinition, typeArguments, replaceArguments = replaceArguments)
                        }
                    }
                    CallableType.Property -> {
                        val variable = callStack.currentSymbolTable().read(functionRefName!!)
                        if (variable is LambdaValue) {
                            return evalFunctionCall(variable.value, extraSymbols = variable.symbolRefs, replaceArguments = replaceArguments)
                        }
                    }
                    else -> {}
                }
                throw RuntimeException("Function `$directName` not found")
            }

            is NavigationNode -> {
                val subject = function.subject.eval()
                if (subject === NullValue) {
                    if (function.operator == "?.") {
                        return NullValue // TODO not always true for extension functions
                    } else {
                        // extension methods of nullable types are allowed to be called
                    }
                }
                when (callableType) {
                    CallableType.ClassMemberFunction -> {
                        if (subject === NullValue) {
                            throw EvaluateNullPointerException(callStack.currentSymbolTable(), callStack.getStacktrace(position))
                        }
                        (subject as? ClassInstance)
                            ?.let { function.resolveSuperKeyword(it) as? ClassInstance }
                            ?.let { instance ->
                                val function = instance.clazz!!.findMemberFunctionByTransformedName(functionRefName!!)
                                if (function != null) {
                                    val subject = if (isSpecialFunction == true) {
                                        instance
                                    } else {
                                        subject
                                    }
                                    return evalClassMemberAnyFunctionCall(
                                        subject = subject,
                                        function = function,
                                        replaceArguments = replaceArguments,
                                    )
                                }
                            }
                        (subject as? PrimitiveValue)
                            ?.clazz
                            ?.findMemberFunctionByTransformedName(functionRefName!!)
                            ?.let { function ->
                                return evalClassMemberAnyFunctionCall(subject, function, replaceArguments = replaceArguments)
                            }
                    }
                    CallableType.ExtensionFunction -> {
                        val function = callStack.currentSymbolTable().findExtensionFunction(functionRefName!!)
                            ?: throw RuntimeException("Analysed function $functionRefName not found")
                        if (subject === NullValue
                            && !function.receiver!!.resolveGenericParameterTypeToUpperBound(function.extraTypeParameters + function.typeParameters, isResolveRootOnly = true).isNullable
                        ) {
                            throw EvaluateNullPointerException(callStack.currentSymbolTable(), callStack.getStacktrace(position))
                        }

                        (subject as? ClassInstance)
                            ?.let { this.function.resolveSuperKeyword(it) as? ClassInstance }
                            ?.let { instance ->
                                val subject = if (isSpecialFunction == true) {
                                    instance
                                } else {
                                    subject
                                }
                                return evalClassMemberAnyFunctionCall(
                                    subject = subject,
                                    function = function,
                                    replaceArguments = replaceArguments,
                                )
                            }

                        return evalClassMemberAnyFunctionCall(subject as RuntimeValue, function, replaceArguments = replaceArguments)
                    }
                    else -> {}
                }
                throw RuntimeException("Class Function `${function.member.name}` not found")
            }

            else -> {
                val variable = function.eval() as? RuntimeValue
                if (variable is LambdaValue) {
                    return evalFunctionCall(variable.value, extraSymbols = variable.symbolRefs, replaceArguments = replaceArguments)
                } else {
                    throw RuntimeException("${variable?.type()} is not callable")
                }
            }
        }
    }

    fun FunctionCallNode.evalFunctionCall(functionNode: CallableNode, extraSymbols: SymbolTable? = null, replaceArguments: Map = emptyMap()): RuntimeValue {
        return evalFunctionCall(this, functionNode, emptyMap(), emptyList(), extraSymbols, replaceArguments).result
    }

    fun evalFunctionCall(
        callNode: FunctionCallNode,
        functionNode: CallableNode,
        extraScopeParameters: Map,
        extraTypeResolutions: List,
        extraSymbols: SymbolTable? = null,
        replaceArguments: Map = emptyMap(),
        subject: RuntimeValue? = null,
    ): FunctionCallResult {
        // TODO optimize to remove most loops
        val isVararg =
            functionNode.valueParameters.firstOrNull()?.modifiers?.contains(FunctionValueParameterModifier.vararg)
                ?: false
        val callArguments = if (isVararg) {
            callNode.arguments.map { a -> a.value.eval() as RuntimeValue? }.toTypedArray()
        } else {
            val callArguments = arrayOfNulls(functionNode.valueParameters.size)
            callNode.arguments.forEachIndexed { i, a ->
                val value = replaceArguments[i] ?: a.value.eval() as RuntimeValue
                val index = if (a.name != null) {
                    functionNode.valueParameters.indexOfFirst { it.name == a.name }
                } else if (i == callNode.arguments.lastIndex && value is LambdaValue) {
                    functionNode.valueParameters.lastIndex
                } else {
                    a.index
                }
                if (index < 0) throw RuntimeException("Named argument `${a.name}` could not be found.")
                callArguments[index] = value
            }
            callArguments
        }

        return evalFunctionCall(callArguments, callNode.typeArguments.toTypedArray(), callNode.position, functionNode, extraScopeParameters, extraTypeResolutions, extraSymbols, replaceArguments, subject)
    }

    fun evalFunctionCall(
        arguments: Array,
        typeArguments: Array,
        callPosition: SourcePosition,
        functionNode: CallableNode,
        extraScopeParameters: Map,
        extraTypeResolutions: List,
        extraSymbols: SymbolTable? = null,
        replaceArguments: Map = emptyMap(),
        subject: RuntimeValue? = null
    ): FunctionCallResult {
        val isVararg = functionNode.valueParameters.firstOrNull()?.modifiers?.contains(FunctionValueParameterModifier.vararg) ?: false
        if (!isVararg && arguments.size != functionNode.valueParameters.size) {
            throw RuntimeException("Arguments size not match. Optional arguments should be specified as null.")
        }

        if (!isVararg) {
            arguments.forEachIndexed { index, it ->
                val parameterNode = functionNode.valueParameters[index]
                if (replaceArguments[index] == null && it == null && parameterNode.defaultValue == null) {
                    throw RuntimeException("Missing parameter `${parameterNode.name} in function call ${functionNode.name}`")
                }
            }
        }

        val classResolver = (functionNode as? FunctionDeclarationNode)
            ?.takeIf { it.transformedRefName != null }
            ?.let { function ->
                val subjectType = subject?.type() as? ObjectType
                subjectType?.let { subjectType ->
                    ClassMemberResolver.create(symbolTable(), subjectType.clazz, subjectType.arguments.map { it.toTypeNode() })
                }
            }
        val resolvedFunction = (functionNode as? FunctionDeclarationNode)
            ?.takeIf { it.transformedRefName != null }
            ?.let { function ->
                classResolver?.findMemberFunctionWithTypeByTransformedName(function.transformedRefName!!)
            }

        val typeParametersReplacedWithArguments = (
                extraTypeResolutions + // add `extraTypeResolutions` at first because class type arguments have a lower precedence
                (classResolver?.let { resolver ->
                    resolvedFunction?.enclosingTypeName?.let { typeName ->
                        resolver.genericResolutionsByTypeName[typeName]!!.map {
                            TypeParameterNode(it.value.position, it.key, it.value)
                        }
                    }
                } ?: emptyList()) +
                functionNode.typeParameters.mapIndexed { index, tp ->
                    TypeParameterNode(tp.position, tp.name, typeArguments[index])
                }
            )
            .associate { it.name to it.typeUpperBound!! }

        // resolve type arguments to DataType first, so that
        // class with same name of function type parameter name is resolved before function type parameter declarations
        val typeArgumentsInDataType = typeParametersReplacedWithArguments.mapValues {
            symbolTable().assertToDataType(it.value)
        }

        val scopeType = if (functionNode is FunctionDeclarationNode) ScopeType.Function else ScopeType.Closure

        val returnType = callStack.currentSymbolTable().assertToDataType(
            // 2nd resolution is needed, because the generic type may not be relevant to the class itself.
            // see test case GenericFunctionAndExtensionFunctionWithGenericClassTest#unrelatedTypeParameter()
            type = (resolvedFunction?.resolvedReturnType ?: functionNode.returnType).resolveGenericParameterTypeArguments(
//                (extraSymbols?.listTypeAliasInThisScope() ?: emptyList()) +
                typeParametersReplacedWithArguments + // typeParametersReplacedWithArguments has higher precedence
                (extraSymbols?.listTypeAliasResolutionInThisScope() ?: emptyMap()).mapValues {
                    it.value.toTypeNode()
                }
            ),
        )

        callStack.push(
            functionFullQualifiedName = functionNode.name,
            isFunctionCall = true,
            scopeType = scopeType,
            callPosition = callPosition,
        )
        try {
            val symbolTable = callStack.currentSymbolTable()
            extraSymbols?.let{
                symbolTable.mergeFrom(callPosition, it)
            }
            extraScopeParameters.forEach {
                symbolTable.declareProperty(callPosition, it.key, TypeNode(callPosition, it.value.type().name, null, false), false) // TODO change to use DataType directly
                symbolTable.assign(it.key, it.value)
            }
            functionNode.typeParameters.forEach {
                symbolTable.declareTypeAlias(callPosition, it.name, it.typeUpperBound)
            }
            typeParametersReplacedWithArguments.forEach {
                if (symbolTable.findTypeAlias(it.key) == null) {
                    symbolTable.declareTypeAlias(callPosition, it.key, it.value) // TODO declare the original upper bound
                }
                symbolTable.declareTypeAliasResolution(callPosition, it.key, typeArgumentsInDataType[it.key]!!)
            }
            val valueParametersWithGenericsResolved = resolvedFunction?.resolvedValueParameterTypes
                ?: functionNode.valueParameters
            val varargListValueArgument = if (isVararg) {
                ListValue(arguments.filterNotNull().toList(), symbolTable().assertToDataType(functionNode.valueParameters.first().type), symbolTable())
            } else null
            functionNode.valueParameters.forEachIndexed { index, it ->
                if (!isVararg && !(functionNode is LambdaLiteralNode && it.name == "_")) {
                    val argumentType = valueParametersWithGenericsResolved[index].type
                    symbolTable.declareProperty(callPosition, it.transformedRefName!!, argumentType, false)
                    symbolTable.assign(
                        name = it.transformedRefName!!,
                        value = replaceArguments[index] ?: arguments[index] ?: (it.defaultValue!!.eval() as RuntimeValue)
                            .also { arguments[index] = it }
                    )
                } else if (isVararg) {
                    val argumentType = varargListValueArgument!!.type().toTypeNode()
                    symbolTable.declareProperty(callPosition, it.transformedRefName!!, argumentType, false)
                    symbolTable.assign(
                        name = it.transformedRefName!!,
                        value = varargListValueArgument,
                    )
                }
                if (!isVararg && (it.type as? FunctionTypeNode)?.receiverType != null) {
                    val type = it.type as FunctionTypeNode

                    fun findTypeParameters(typeNode: TypeNode, result: MutableSet) {
                        result += typeNode.name
                        typeNode.arguments?.forEach {
                            findTypeParameters(it, result)
                        }
                    }
                    val allPossibleTypeParameters = mutableSetOf()
                    (setOfNotNull(type.receiverType, type.returnType) + (type.parameterTypes ?: emptyList()))
                        .forEach { findTypeParameters(it, allPossibleTypeParameters) }

                    symbolTable.declareExtensionFunction(
                        position = it.position,
                        name = type.extensionFunctionRefName!!,
                        node = object : FunctionDeclarationNode(
                            position = it.position,
                            name = "",
                            extraTypeParameters = functionNode.typeParameters.filter {
                                it.name in allPossibleTypeParameters
                            },
                            receiver = type.receiverType,
                            declaredReturnType = type.returnType,
                            valueParameters = (type.parameterTypes ?: emptyList()).map {
                                FunctionValueParameterNode(
                                    position = it.position,
                                    name = "",
                                    declaredType = it,
                                    defaultValue = null,
                                    modifiers = emptySet(),
                                )
                            },
                            body = null,
                        ) {
                            override fun execute(
                                interpreter: Interpreter,
                                receiver: RuntimeValue?,
                                lambdaArguments: List,
                                typeArguments: Map
                            ): RuntimeValue {
                                return ((replaceArguments[index] ?: arguments[index]) as? LambdaValue)
                                    ?.execute(lambdaArguments.toTypedArray())
                                    ?: NullValue
                            }
                        }
                    )
                }
            }

            val arguments = if (isVararg) {
                arrayOf(varargListValueArgument)
            } else {
                arguments
            }

            // execute function
            val returnValue = try {
                val result = functionNode.execute(this, subject, arguments.toList() as List, typeArgumentsInDataType.toMap())
                if (returnType is UnitType) {
                    UnitValue
                } else {
                    result
                }
            } catch (r: NormalReturnException) {
                if (r.returnToLabel.isEmpty()) {
                    if (functionNode.labelName != null) {
                        throw RuntimeException("Returning to a non-function callable")
                    }
                } else {
                    if (functionNode.labelName == null) {
                        throw RuntimeException("Returning to a non-lambda callable")
                    } else if (functionNode.labelName != r.returnToLabel) {
                        throw RuntimeException("Returning to a lambda with mismatching label")
                    }
                }

                r.value
            }

            log.v { "Fun Return $returnValue; symbolTable = $symbolTable" }
            if (!returnType.isCastableFrom(returnValue.type())) {
                throw RuntimeException("Return value's type ${returnValue.type().descriptiveName} cannot be casted to ${returnType.descriptiveName} in function `${functionNode.name}` at ${functionNode.position}")
            }

            return FunctionCallResult(returnValue, symbolTable)
        } finally {
            callStack.pop(scopeType)
        }
    }

    fun FunctionCallNode.evalCreateClassInstance(clazz: ClassDefinition, typeArguments: List, replaceArguments: Map = emptyMap()): ClassInstance {
        callStack.push(functionFullQualifiedName = "class", scopeType = ScopeType.ClassInitializer, callPosition = this.position)
        try {
            // TODO generalize duplicated code
            val parameters = clazz.primaryConstructor?.parameters ?: emptyList()
            val callArguments = arrayOfNulls(parameters.size)
            arguments.forEach { a ->
                val index = if (a.name != null) {
                    parameters.indexOfFirst { it.parameter.name == a.name }
                } else {
                    a.index
                }
                if (index < 0) throw RuntimeException("Named argument `${a.name}` could not be found.")
                callArguments[index] = replaceArguments[index] ?: a.value.eval() as RuntimeValue
            }
            callArguments.forEachIndexed { index, it ->
                val parameterNode = parameters[index].parameter
                if (it == null && parameterNode.defaultValue == null) {
                    throw RuntimeException("Missing parameter `${parameterNode.name} in constructor call of ${clazz.fullQualifiedName}`")
                }
            }

            val typeArgumentByName = clazz.typeParameters.mapIndexed { index, tp ->
                tp.name to typeArguments[index]
            }.toMap()

            val symbolTable = callStack.currentSymbolTable()
            clazz.primaryConstructor?.parameters?.forEachIndexed { index, it ->
                // no need to use transformedRefName as duplicated declarations are not possible here
                val value = callArguments[index] ?: (it.parameter.defaultValue!!.eval() as RuntimeValue)
                symbolTable.declareProperty(it.position, it.parameter.transformedRefName!!, it.parameter.type.resolveGenericParameterTypeArguments(typeArgumentByName), false)
                symbolTable.assign(it.parameter.transformedRefName!!, value)
                callArguments[index] = value
            }

            return clazz.construct(this@Interpreter, callArguments as Array, typeArguments.map { symbolTable().assertToDataType(it) }.toTypedArray(), position)
        } finally {
            callStack.pop(ScopeType.ClassInitializer)
        }
    }

    fun constructClassInstance(callArguments: Array, callPosition: SourcePosition, typeArguments: Array, clazz: ClassDefinition): ClassInstance {
        val parentInstance = clazz.superClassInvocation?.let { superClassInvocation ->
            callStack.push("super", ScopeType.Class, SourcePosition("TODO", 1, 1)) // TODO filename
            typeArguments.forEachIndexed { index, dataType ->
                val typeParameter = clazz.typeParameters[index]
                symbolTable().declareTypeAlias(typeParameter.position, typeParameter.name, typeParameter.typeUpperBound)
                symbolTable().declareTypeAliasResolution(typeParameter.position, typeParameter.name, dataType)
            }
            try {
                superClassInvocation.eval() as ClassInstance?
            } finally {
                callStack.pop(ScopeType.Class)
            }
        }

        val symbolTable = callStack.currentSymbolTable()
        val instance = ClassInstance(symbolTable, clazz.fullQualifiedName, clazz, typeArguments.toList(), parentInstance = parentInstance)
        val properties = clazz.primaryConstructor?.parameters?.filter { it.isProperty }?.map { it.parameter.transformedRefName!! }?.toMutableSet() ?: mutableSetOf()

        val nonPropertyArguments = mutableMapOf>()
        clazz.primaryConstructor?.parameters?.forEachIndexed { index, it ->
            val value = callArguments[index]
            if (it.isProperty) {
                instance.assign(name = it.parameter.transformedRefName!!, value = value)
//                    instance.memberPropertyValues[it.parameter.transformedRefName!!] = value
            } else {
                nonPropertyArguments[it.parameter.transformedRefName!!] = Pair(it, value)
            }
        }

        // move nonPropertyArguments from outer scope into inner scope
        nonPropertyArguments.keys.forEach {
            symbolTable.undeclareProperty(it)
        }
//            clazz.primaryConstructor?.parameters?.forEach {
//                symbolTable.undeclareProperty(it.parameter.transformedRefName!!)
//            }

        // variable "this" is available after primary constructor
        symbolTable.declareProperty(callPosition, "this", TypeNode(callPosition, instance.clazz!!.fullQualifiedName, typeArguments.map { it.toTypeNode() }.emptyToNull(), false), false)
        symbolTable.assign("this", instance)
        symbolTable.declareProperty(callPosition, "this/${instance.clazz!!.fullQualifiedName}", TypeNode(callPosition, instance.clazz!!.fullQualifiedName, typeArguments.map { it.toTypeNode() }.emptyToNull(), false), false)
        symbolTable.assign("this/${instance.clazz!!.fullQualifiedName}", instance)
//        symbolTable.registerTransformedSymbol(callPosition, IdentifierClassifier.Property, "this", "this")
        symbolTable.registerTransformedSymbol(callPosition, IdentifierClassifier.Property, "this/${instance.clazz!!.fullQualifiedName}", "this")

//            instance.memberPropertyValues.forEach {
//                symbolTable.putPropertyHolder(instance.clazz!!.memberPropertyNameToTransformedName[it.key]!!, it.value)
//            }

        if (instance.clazz?.superClass != null) {
            // a hack to resolve the "super" keyword. See documentation
            symbolTable.declareProperty(
                callPosition,
                "super",
                TypeNode(SourcePosition.NONE, instance.clazz!!.fullQualifiedName, typeArguments.map { it.toTypeNode() }.emptyToNull(), false),
                false
            )
            symbolTable.assign("super", instance)
        }

        val typeParametersAndArguments = clazz.typeParameters.mapIndexed { index, tp ->
            TypeParameterNode(tp.position, tp.name, typeArguments[index].toTypeNode())
        }
        val typeArgumentByName = typeParametersAndArguments.associate {
            it.name to it.typeUpperBound!!
        }

        clazz!!.orderedInitializersAndPropertyDeclarations.forEach {
            callStack.push(
                functionFullQualifiedName = "init-property",
                scopeType = ScopeType.ClassInitializer,
                callPosition = callPosition
            )
            try {
                val innerSymbolTable = callStack.currentSymbolTable()
                nonPropertyArguments.forEach {
                    innerSymbolTable.declareProperty(callPosition, it.value.first.transformedRefNameInBody!!, it.value.first.parameter.type.resolveGenericParameterTypeArguments(typeArgumentByName), false)
                    innerSymbolTable.assign(it.value.first.transformedRefNameInBody!!, it.value.second)
                }
                when (it) {
                    is PropertyDeclarationNode -> {
                        properties += it.transformedRefName!!
                        val value = it.initialValue?.eval() as RuntimeValue?
                        value?.let { value ->
                            instance.assign(name = it.transformedRefName!!, value = value)
                        }
                    }

                    is ClassInstanceInitializerNode -> {
                        val init = FunctionDeclarationNode(
                            position = it.position,
                            name = "init",
                            declaredReturnType = TypeNode(it.position, "Unit", null, false),
                            valueParameters = emptyList(),
                            body = it.block
                        )
                        evalFunctionCall(
                            callNode = FunctionCallNode(
                                function = init, /* not used */
                                arguments = emptyList(),
                                declaredTypeArguments = emptyList(),
                                position = callPosition,
                            ),
                            functionNode = init,
                            extraScopeParameters = emptyMap(),
                            extraTypeResolutions = typeParametersAndArguments /* type arguments */,
                        )
                    }

                    else -> Unit
                }
            } finally {
                callStack.pop(ScopeType.ClassInitializer)
            }
        }
        return instance
    }

//    fun FunctionCallNode.evalClassMemberFunctionCall(subject: ClassInstance, member: ClassMemberReferenceNode): RuntimeValue {
//        val function = subject.clazz!!.memberFunctions[member.name] ?: throw EvaluateRuntimeException("Member function `${member.name}` not found")
//        return evalClassMemberAnyFunctionCall(subject, function)
//    }

    fun FunctionCallNode.evalClassMemberAnyFunctionCall(subject: RuntimeValue, function: FunctionDeclarationNode, replaceArguments: Map = emptyMap()): RuntimeValue {
        return evalClassMemberAnyFunctionCall(position, subject, function.receiver, function) { typeResolutions ->
            evalFunctionCall(
                callNode = this.copy(function = function),
                functionNode = function,
                extraScopeParameters = emptyMap(),
                extraTypeResolutions = typeResolutions /* type arguments */,
                replaceArguments = replaceArguments,
                subject = subject,
            )
        }
    }

    fun evalClassMemberAnyFunctionCall(position: SourcePosition, subject: RuntimeValue, function: CallableNode, arguments: Array, typeArguments: Array = emptyArray()): RuntimeValue {
        return evalClassMemberAnyFunctionCall(position, subject, subject.type().toTypeNode(), function) { typeResolutions ->
            evalFunctionCall(
                arguments = arguments,
                typeArguments = typeArguments,
                callPosition = position,
                functionNode = function,
                extraScopeParameters = emptyMap(),
                extraTypeResolutions = typeResolutions,
                subject = subject,
            )
        }
    }

    private fun evalClassMemberAnyFunctionCall(position: SourcePosition, subject: RuntimeValue, receiverType: TypeNode?, function: CallableNode, callOperation: (typeResolutions: List) -> FunctionCallResult): RuntimeValue {
        callStack.push(functionFullQualifiedName = "class", scopeType = ScopeType.ClassMemberFunction, callPosition = position)
        try {
            val symbolTable = callStack.currentSymbolTable()
            val declaredThisClassNames = mutableSetOf()
            if (subject.type() is ObjectType) {
                var clazz: ClassDefinition? = (subject.type() as ObjectType).clazz
                while (clazz != null) {
                    declaredThisClassNames += clazz.fullQualifiedName
                    symbolTable.declareProperty(position, "this/${clazz.fullQualifiedName}", subject.type().toTypeNode(), false)
                    symbolTable.assign("this/${clazz.fullQualifiedName}", subject)
                    clazz = clazz.superClass
                }
            } else {
                declaredThisClassNames += subject.type().name
                symbolTable.declareProperty(position, "this/${subject.type().name}", subject.type().toTypeNode(), false)
                symbolTable.assign("this/${subject.type().name}", subject)
            }
            if (receiverType != null) {
                val receiverIdentifier = receiverType.resolveGenericParameterTypeToUpperBound(function.typeParameters).descriptiveName()
                if (!declaredThisClassNames.contains(receiverIdentifier)) {
                    declaredThisClassNames += receiverIdentifier
                    symbolTable.declareProperty(position, "this/$receiverIdentifier", subject.type().toTypeNode(), false)
                    symbolTable.assign("this/$receiverIdentifier", subject)
                }
            }
            symbolTable.declareProperty(position, "this", subject.type().toTypeNode(), false)
            symbolTable.assign("this", subject)
//            symbolTable.registerTransformedSymbol(position, IdentifierClassifier.Property, "this", "this")
            symbolTable.registerTransformedSymbol(position, IdentifierClassifier.Property, "this/${subject.type().name}", "this")

            if (subject is ClassInstance) {
                // a hack to resolve "super". See documentation
                val parentInstance = subject.parentInstance ?: subject
                symbolTable.declareProperty(position, "super", subject.type().toTypeNode(), false)
                symbolTable.assign("super", subject)
                symbolTable.registerTransformedSymbol(position, IdentifierClassifier.Property, "super", "super")
            }

//            // TODO optimize to only copy needed members
//            if (subject is ClassInstance) {
//                subject.memberPropertyValues.forEach {
//                    symbolTable.putPropertyHolder(subject.clazz!!.memberPropertyNameToTransformedName[it.key]!!, it.value)
//                }
//            }

            val instanceGenericTypeResolutions = if (subject is ClassInstance) {
                subject.clazz!!.typeParameters.mapIndexed { index, it ->
                    TypeParameterNode(it.position, it.name, subject.typeArguments[index].toTypeNode())
                }
            } else emptyList()

            val result = callOperation(instanceGenericTypeResolutions)

            return result.result
        } finally {
            callStack.pop(ScopeType.ClassMemberFunction)
        }
    }

    fun BlockNode.eval(): RuntimeValue {
        // additional scope because new variables can be declared in blocks of `if`, `while`, etc.
        // also, function parameters can be shadowed
        callStack.push(functionFullQualifiedName = null, scopeType = type, callPosition = position)
        val result = try {
            statements.map { it.eval() as? RuntimeValue }.lastOrNull() ?: UnitValue
        } finally {
            callStack.pop(type)
        }
        return result
    }

    fun ReturnNode.eval() {
        val value = (value?.eval() ?: UnitValue) as RuntimeValue
        throw NormalReturnException(returnToAddress = returnToAddress, returnToLabel = returnToLabel, value = value)
    }

    fun BreakNode.eval() {
        throw NormalBreakException()
    }

    fun ContinueNode.eval() {
        throw NormalContinueException()
    }

    fun IfNode.eval(): RuntimeValue {
        val conditionalValue = condition.eval() as BooleanValue
        return if (conditionalValue.value) {
            trueBlock?.eval() ?: UnitValue
        } else {
            falseBlock?.eval() ?: UnitValue
        }
    }

    fun WhileNode.eval() {
//        if (conditionalValue.value) {
//            if (body == null || body.statements.isEmpty()) {
//                throw NotPermittedOperationException("Infinite loop is not allowed")
//            }
//        }
        // TODO detect infinite loop
        try {
            while ((condition.eval() as BooleanValue).value) {
                try {
                    body?.eval()
                } catch (_: NormalContinueException) {}
            }
        } catch (_: NormalBreakException) {}
    }

    fun DoWhileNode.eval() {
        try {
            do {
                try {
                    body?.eval()
                } catch (_: NormalContinueException) {}
            } while ((condition.eval() as BooleanValue).value)
        } catch (_: NormalBreakException) {}
    }

    fun ClassDeclarationNode.eval() {
        val declarationScope = callStack.currentSymbolTable()
        val classType = TypeNode(
            position = position,
            name = fullQualifiedName,
            arguments = typeParameters.map { TypeNode(it.position, it.name, null, false) }.emptyToNull(),
            isNullable = false,
        )

        val interfaceInvocations: List
        val superClassInvocation: FunctionCallNode?
        if (isInterface) {
            superInvocations?.firstOrNull { it is FunctionCallNode }
                ?.let {
                    throw RuntimeException("Interface cannot inherit a class")
                }
            interfaceInvocations = superInvocations?.filterIsInstance() ?: emptyList()
            superClassInvocation = null
        } else {
            val superClassInvocations = superInvocations?.filterIsInstance()
            if ((superClassInvocations?.size ?: 0) > 1) {
                throw RuntimeException("A class can only inherit at most one other class")
            }
            interfaceInvocations = superInvocations?.filterIsInstance() ?: emptyList()
            superClassInvocation = superClassInvocations?.firstOrNull()
        }

        val superClass = (superClassInvocation?.function as? TypeNode)
            ?.let { declarationScope.findClass(it.name) ?: throw RuntimeException("Super class `${it.name}` not found") }
            ?.first

        val clazz: ClassDefinition
        callStack.push(fullQualifiedName, ScopeType.Class, position)
        try {
            typeParameters.forEach {
                callStack.currentSymbolTable().declareTypeAlias(position, it.name, it.typeUpperBound)
            }

            declarationScope.declareClass(position, ClassDefinition(
                currentScope = callStack.currentSymbolTable(),
                name = name,
                modifiers = modifiers,
                isInterface = isInterface,
                fullQualifiedName = fullQualifiedName,
                typeParameters = typeParameters,
                isInstanceCreationAllowed = true,
                primaryConstructor = primaryConstructor,
                rawMemberProperties = ((primaryConstructor?.parameters
                    ?.filter { it.isProperty }
                    ?.map {
                        val p = it.parameter
                        PropertyDeclarationNode(
                            position = p.position,
                            name = p.name,
                            declaredModifiers = it.modifiers,
                            typeParameters = emptyList(),
                            receiver = classType,
                            declaredType = p.type,
                            isMutable = it.isMutable,
                            initialValue = p.defaultValue,
                            transformedRefName = p.transformedRefName,
                        )
                    } ?: emptyList()) +
                        declarations.filterIsInstance()),
                memberFunctions = declarations
                    .filterIsInstance()
                    .filter { it.receiver == null },
//                    .associateBy { it.transformedRefName!! },
                orderedInitializersAndPropertyDeclarations = declarations
                    .filter { it is ClassInstanceInitializerNode || it is PropertyDeclarationNode },
                declarations = declarations,
                superClassInvocation = superClassInvocation,
                superClass = superClass,
                superInterfaceTypes = interfaceInvocations,
                superInterfaces = interfaceInvocations.map {
                    val clazz = symbolTable().findClass(it.name)?.first
                        ?: throw RuntimeException("Interface ${it.name} cannot be found")
                    if (!clazz.isInterface) {
                        throw RuntimeException("${it.name} is not an interface")
                    }
                    clazz
                },
            ).also {
                clazz = it
                it.attachToInterpreter(this@Interpreter)
            })
            // register extension functions in global scope
            declarations
                .filterIsInstance()
                .filter { it.receiver != null }
                .forEach { globalScope.declareExtensionFunction(it.position, it.transformedRefName!!, it) }
        } finally {
            callStack.pop(ScopeType.Class)
        }

        // companion object
        // TODO create only if a companion object is declared
        callStack.currentSymbolTable().declareClass(position, ClassDefinition(
            currentScope = callStack.currentSymbolTable(),
            name = "$name.Companion",
            fullQualifiedName = "$fullQualifiedName.Companion",
            modifiers = emptySet(),
            typeParameters = emptyList(),
            isInstanceCreationAllowed = false,
            orderedInitializersAndPropertyDeclarations = emptyList(),
            declarations = emptyList(),
            rawMemberProperties = emptyList(),
            memberFunctions = buildList {
                if (ClassModifier.enum in modifiers) {
                    add(CustomFunctionDeclarationNode(
                        CustomFunctionDefinition(
                            position = position,
                            receiverType = "$fullQualifiedName.Companion",
                            functionName = "valueOf",
                            returnType = classType.descriptiveName(),
                            parameterTypes = listOf(CustomFunctionParameter("value", "String")),
                            executable = { interpreter, receiver, args, typeArgs ->
                                val value: String = (args[0] as StringValue).value
                                clazz.enumValues[value]
                                    ?: throwEvalRuntimeException(
                                        position,
                                        "Enum value '$value' not found in class $name"
                                    )
                            }
                        ),
                        transformedRefName = executionEnvironment.findGeneratedMapping(
                            type = ExecutionEnvironment.SymbolType.Function,
                            receiverType = "$fullQualifiedName.Companion",
                            name = "valueOf",
                        ).transformedName,
                    ))
                }
            },
            primaryConstructor = null
        ).also { it.attachToInterpreter(this@Interpreter) })

        // creating enum values
        if (ClassModifier.enum in modifiers) {
            clazz.enumValues = enumEntries.associate {
                val instance = it.call!!.eval() as ClassInstance
                it.name to instance
            }

            ExtensionProperty(
                declaredName = "entries",
                receiver = "$fullQualifiedName.Companion",
                type = "List<${classType.descriptiveName()}>",
                getter = { interpreter, _, _ ->
                    ListValue(clazz.enumValues.values.toList() as List, interpreter.symbolTable().assertToDataType(classType), interpreter.symbolTable())
                }
            ).also {
                val transformedName = executionEnvironment.findGeneratedMapping(
                    type = ExecutionEnvironment.SymbolType.ExtensionProperty,
                    receiverType = "$fullQualifiedName.Companion",
                    name = "entries",
                ).transformedName
                it.transformedName = transformedName
                symbolTable().declareExtensionProperty(position, transformedName, it)
            }
        }
    }

    fun NavigationNode.eval(): RuntimeValue {
        val obj = (subject.eval() as RuntimeValue)
            .let { resolveSuperKeyword(it) }
//        return obj.memberPropertyValues[member.transformedRefName!!]!!

        if (memberType == NavigationNode.MemberType.Extension && transformedRefName != null) {
            val extensionProperty = symbolTable().findExtensionProperty(transformedRefName!!)
                ?: throw RuntimeException("Extension property `${member.name}` on receiver `${obj.type().nameWithNullable}` could not be found")

            if (obj === NullValue && !extensionProperty.receiverType!!.isNullable) {
                if (operator == ".") {
                    throw EvaluateNullPointerException(callStack.currentSymbolTable(), callStack.getStacktrace(position))
                } else if (operator == "?.") {
                    return obj
                }
            }

            val typeArgumentsMap = extensionProperty.typeArgumentsMap(obj.type())

            extensionProperty.getter?.let { getter ->
                return getter(this@Interpreter, obj, typeArgumentsMap)
            }
        }
        if (memberType == NavigationNode.MemberType.Enum) {
            val originalClassName = (obj as ClassInstance).clazz!!.fullQualifiedName.removeSuffix(".Companion")
            val enumClazz = symbolTable().findClass(originalClassName)?.first
                ?: throw RuntimeException("Cannot find class $originalClassName")
            if (ClassModifier.enum !in enumClazz.modifiers) {
                throw RuntimeException("Class `$originalClassName` is not an enum class")
            }
            return enumClazz.enumValues[member.name]
                ?: throw RuntimeException("No such enum `${member.name}` in class `$originalClassName`")
        }

        if (obj === NullValue) {
            if (operator == "?.") {
                return NullValue
            }
            throw EvaluateNullPointerException(callStack.currentSymbolTable(), callStack.getStacktrace(position))
        }
        obj as? ClassInstance ?: throw RuntimeException("Cannot access member `${member.name}` for type `${obj.type().nameWithNullable}`")
        // before type resolution is implemented in SemanticAnalyzer, reflect from clazz as a slower alternative
        return when (val r = obj.read(interpreter = this@Interpreter, name = obj.clazz!!.findMemberPropertyTransformedName(member.name)!!)) {
            is RuntimeValue -> r
            /*is FunctionDeclarationNode -> {
                FunctionCallNode(
                    function = r,
                    arguments = emptyList(),
                    position = SourcePosition(1, 1)
                ).evalClassMemberAnyFunctionCall(obj, r)
            } // TODO remove */
            else -> throw UnsupportedOperationException()
        }
    }

    fun IndexOpNode.eval(): RuntimeValue {
        if (hasFunctionCall == true) {
            return call!!.eval()
        } else {
            return subject.eval() as RuntimeValue
        }
    }

    fun AsOpNode.eval(): RuntimeValue {
        val value = expression.eval() as RuntimeValue
        val targetType = symbolTable().typeNodeToDataType(type) ?: throw RuntimeException("Unknown type `${type.descriptiveName()}`")
        return if (targetType.isCastableFrom(value.type())) {
            value
        } else if (isNullable) {
            NullValue
        } else {
            throw EvaluateTypeCastException(symbolTable(), callStack.getStacktrace(position), value.type().descriptiveName, targetType.descriptiveName)
        }
    }

    fun LambdaLiteralNode.eval(): RuntimeValue {
        val refs = this.accessedRefs!!
        val currentSymbolTable = callStack.currentSymbolTable()
        val runtimeRefs = SymbolTable(Int.MAX_VALUE, "lambda-symbol-ref", ScopeType.Closure, currentSymbolTable.rootScope)
        refs.properties.forEach {
            runtimeRefs.putPropertyHolder(it, false /* TODO review */, currentSymbolTable.getPropertyHolder(it))
        }
        refs.functions.forEach {
            runtimeRefs.declareFunction(position, it, currentSymbolTable.findFunction(it)!!.first)
        }
        refs.extensionFunctions.forEach {
            val extensionFunction = currentSymbolTable.findExtensionFunctionWithReceiver(it)!!
            runtimeRefs.declareExtensionFunction(position, it, extensionFunction.second, extensionFunction.first)
        }
        refs.classes.forEach {
            runtimeRefs.declareClass(position, currentSymbolTable.findClass(it)!!.first)
        }
        refs.typeAlias.forEach {
//            runtimeRefs.declareTypeAlias(it, currentSymbolTable.findTypeAlias(it)!!.first.toTypeNode(), currentSymbolTable)
            val resolution = currentSymbolTable.findTypeAliasResolution(it)!!.toTypeNode()
            runtimeRefs.declareTypeAlias(position, it, currentSymbolTable.findTypeAlias(it)!!.first.toTypeNode(), currentSymbolTable)
            runtimeRefs.declareTypeAliasResolution(position, it, resolution, currentSymbolTable)
        }

//        fun processTypeParameter(dataType: DataType) {
//            when (dataType) {
//                is TypeParameterType -> {
//                    runtimeRefs.declareTypeAlias(dataType.name, dataType.upperBound.toTypeNode())
//                }
//                is FunctionType -> {
//                    dataType.arguments.forEach { processTypeParameter(it) }
//                    processTypeParameter(dataType.returnType)
//                }
//                is ObjectType -> {
//                    dataType.arguments.forEach { processTypeParameter(it) }
//                }
//                else -> {}
//            }
//        }
        val lambdaType = callStack.currentSymbolTable().typeNodeToDataType(type!!) as FunctionType
//        processTypeParameter(lambdaType)

        return LambdaValue(this, lambdaType, runtimeRefs, this@Interpreter)
    }

    fun InfixFunctionCallNode.eval(): RuntimeValue {
        call?.eval()?.let {
            if (functionName == "!in") {
                return BooleanValue((it as BooleanValue).value.not())
            }
            return it
        }

        val n1 = node1.eval() as RuntimeValue
        return when (functionName) {
            "to" -> {
                val n2 = node2.eval() as RuntimeValue
                PairValue(n1 to n2, n1.type(), n2.type(), symbolTable())
            }
            "is", "!is" -> {
                val type = symbolTable().assertToDataType(node2 as TypeNode)
                val isType = type.isAssignableFrom(n1.type())
                BooleanValue(if (functionName == "is") isType else !isType)
            }
            else -> throw RuntimeException("Unknown infix function `$functionName`")
        }
    }

    fun ElvisOpNode.eval(): RuntimeValue {
        val result = primaryNode.eval() as RuntimeValue
        if (result != NullValue) {
            return result
        }
        return fallbackNode.eval() as RuntimeValue
    }

    fun ThrowNode.eval(): RuntimeValue {
        var initialResult = value.eval() as RuntimeValue
        var result: RuntimeValue? = initialResult
        while (result !is ThrowableValue && result is ClassInstance) {
            result = result.parentInstance
        }
        if (result !is ThrowableValue) {
            throw EvaluateTypeCastException(
                currentScope = symbolTable(),
                stacktrace = callStack.getStacktrace(position),
                valueType = initialResult.type().descriptiveName,
                targetType = "Throwable",
            )
        }
        result = ThrowableValue(
            currentScope = symbolTable(),
            message = result.message,
            cause = result.cause,
            stacktrace = result.stacktrace,
            thisClazz = symbolTable().findClass(initialResult.type().name)!!.first,
        )
        throw EvaluateRuntimeException(stacktrace = callStack.getStacktrace(position), error = result)
    }

    fun throwEvalRuntimeException(position: SourcePosition, message: String): Nothing {
        val stacktrace = callStack.getStacktrace(position)
        val error = ExceptionValue(
            currentScope = symbolTable(),
            message = message,
            cause = null,
            stacktrace = stacktrace,
            thisClazz = ExceptionValue.clazz,
        )
        throw EvaluateRuntimeException(stacktrace = stacktrace, error = error)
    }

    fun TryNode.eval(): RuntimeValue {
        try {
            return mainBlock.eval() as RuntimeValue
        } catch (e: EvaluateRuntimeException) {
            for (catch in catchBlocks) {
                if (symbolTable().assertToDataType(catch.catchType).isAssignableFrom(e.error.type())) {
                    return catch.eval(e.error)
                }
            }
            throw e
        } catch (e: Throwable) {
            for (catch in catchBlocks) {
                if (catch.catchType.name == "Throwable") {
                    return catch.eval(e.toValue())
                }
            }
            throw e
        } finally {
            finallyBlock?.eval()
        }
    }

    fun Throwable.toValue(): ThrowableValue {
        return ThrowableValue(symbolTable(), message, cause?.toValue(), emptyList(), this.fullClassName)
    }

    fun CatchNode.eval(value: ThrowableValue): RuntimeValue {
        callStack.push("", ScopeType.Catch, position)
        return try {
            valueTransformedRefName?.let { valueTransformedRefName ->
                symbolTable().declareProperty(
                    position = position,
                    name = valueTransformedRefName,
                    type = catchType,
                    isMutable = false,
                )
                symbolTable().assign(name = valueTransformedRefName, value = value)
            }
            block.eval()
        } finally {
            callStack.pop(ScopeType.Catch)
        }
    }

    fun WhenNode.eval(): RuntimeValue {
        callStack.push("", ScopeType.WhenOuter, position)
        try {
            val subjectValue = subject?.value?.eval() as? RuntimeValue ?: UnitValue
            if (subject?.hasValueDeclaration() == true) {
                subject.valueTransformedRefName?.let { valueTransformedRefName ->
                    symbolTable().declareProperty(
                        position = position,
                        name = valueTransformedRefName,
                        type = subject.type!!,
                        isMutable = false,
                    )
                    symbolTable().assign(name = valueTransformedRefName, value = subjectValue)
                }
            }
            entries.forEach { entry ->
                if (entry.conditions.isEmpty() || entry.conditions.any {
                        when (it.testType) {
                            WhenConditionNode.TestType.TypeTest -> {
                                val type = symbolTable().assertToDataType(it.expression as TypeNode)
                                type.isAssignableFrom(subjectValue.type())
                                    .let { result -> if (it.isNegateResult) !result else result }
                            }
                            WhenConditionNode.TestType.RangeTest -> {
                                (it.call!!.eval(replaceArguments = mapOf(0 to subjectValue)) as BooleanValue).value
                                    .let { result -> if (it.isNegateResult) !result else result }
                            }
                            else -> {
                                val evalExprResult = it.expression.eval()
                                if (subject == null) {
                                    (evalExprResult as BooleanValue).value
                                } else {
                                    evalExprResult == subjectValue
                                }
                            }
                        }
                    }
                ) {
                    return entry.body.eval()
                }
            }
            throw RuntimeException("No match for `when` expression at $position")
        } finally {
            callStack.pop(ScopeType.WhenOuter)
        }
    }

    fun ForNode.eval(): RuntimeValue {
        val subjectValue = subject.eval() as RuntimeValue
        callStack.push("", ScopeType.For, position)

        fun FunctionCallNode.enrichIterableCall(receiverType: DataType): FunctionCallNode {
            val functionName = (function as NavigationNode).member.name
            val actualFunction = symbolTable().findFunctionOrExtensionFunctionIncludingSuperclassesByDeclaredName(
                receiverType.toTypeNode(), functionName
            ).single()
            val functionReceiverType = actualFunction.receiver!!
            val functionTypeParameters = actualFunction.typeParameters
            val inferredTypeArguments: List

            if (functionTypeParameters.isNotEmpty()) {
                var type: DataType? = receiverType
                if (type != null && type.name != functionReceiverType.name) {
                    type = (type as? ObjectType)?.findSuperType(functionReceiverType.name)
                }
                if (type == null && type !is ObjectType) {
                    throw RuntimeException("Enrich fail -- Receiver type of `$functionName` ${functionReceiverType.descriptiveName()} is not found")
                }
                val functionReceiverClazzTypeParameters = (type as ObjectType).clazz.typeParameters
                val functionReceiverClazzTypeArguments = (type as ObjectType).arguments
                val functionReceiverClazzTypeArgumentsMap = functionReceiverClazzTypeParameters.mapIndexed { i, tp ->
                    tp.name to functionReceiverClazzTypeArguments[i]
                }.toMap()
                inferredTypeArguments = functionTypeParameters.map {
                    functionReceiverClazzTypeArgumentsMap[it.name]!!.toTypeNode()
                }
            } else {
                inferredTypeArguments = emptyList()
            }

            return copy(
                functionRefName = symbolTable().findFunctionOrExtensionFunctionIncludingSuperclassesByDeclaredName(
                    receiverType.toTypeNode(), functionName
                ).single().transformedRefName,
                inferredTypeArguments = inferredTypeArguments,
            )
        }

        try {
            symbolTable().declareProperty(subject.position, "#subject", subjectValue.type().toTypeNode(), false)
            symbolTable().assign("#subject", subjectValue)

            // TODO move the call lookups to Semantic Analyzer. Currently impossible because runtime class type member always has higher priority than compile-time type
            val iteratorValue = FunctionCallNode(
                function = NavigationNode(
                    position = position,
                    subject = VariableReferenceNode(position, "#subject"),
                    operator = ".",
                    member = ClassMemberReferenceNode(position, "iterator")
                ),
                arguments = emptyList(),
                declaredTypeArguments = emptyList(),
                position = position,
                callableType = CallableType.ExtensionFunction,
            ).enrichIterableCall(subjectValue.type()).eval()
            symbolTable().declareProperty(subject.position, "#iterator", iteratorValue.type().toTypeNode(), false)
            symbolTable().assign("#iterator", iteratorValue)
            val hasNextCall = FunctionCallNode(
                function = NavigationNode(
                    position = position,
                    subject = VariableReferenceNode(position, "#iterator"),
                    operator = ".",
                    member = ClassMemberReferenceNode(position, "hasNext")
                ),
                arguments = emptyList(),
                declaredTypeArguments = emptyList(),
                position = position,
                callableType = CallableType.ExtensionFunction,
            ).enrichIterableCall(iteratorValue.type())
            val nextCall = FunctionCallNode(
                function = NavigationNode(
                    position = position,
                    subject = VariableReferenceNode(position, "#iterator"),
                    operator = ".",
                    member = ClassMemberReferenceNode(position, "next")
                ),
                arguments = emptyList(),
                declaredTypeArguments = emptyList(),
                position = position,
                callableType = CallableType.ExtensionFunction,
            ).enrichIterableCall(iteratorValue.type())

            while ((hasNextCall.eval() as BooleanValue).value) {
                val nextValue = nextCall.eval()

                variables.forEach {
                    symbolTable().declareProperty(
                        position = position,
                        name = it.transformedRefName!!,
                        type = it.type,
                        isMutable = false,
                    )
                    symbolTable().assign(name = it.transformedRefName!!, value = nextValue)
                }

                body.eval()

                variables.forEach {
                    symbolTable().undeclareProperty(it.transformedRefName!!)
                }
            }
        } finally {
            callStack.pop(ScopeType.For)
        }
        return UnitValue
    }

    fun StringNode.eval(): StringValue {
        return StringValue(nodes.joinToString("") { (it.eval() as RuntimeValue).convertToString() })
    }

    fun StringLiteralNode.eval() = StringValue(content)

    fun IntegerNode.eval() = IntValue(value)
    fun LongNode.eval() = LongValue(value)
    fun DoubleNode.eval() = DoubleValue(value)
    fun BooleanNode.eval() = BooleanValue(value)
    fun CharNode.eval() = CharValue(value)
    fun NullNode.eval() = NullValue
    fun ValueNode.eval() = value

    fun StringValue(value: String) = StringValue(value, symbolTable())
    fun IntValue(value: Int) = IntValue(value, symbolTable())
    fun LongValue(value: Long) = LongValue(value, symbolTable())
    fun DoubleValue(value: Double) = DoubleValue(value, symbolTable())
    fun BooleanValue(value: Boolean) = BooleanValue(value, symbolTable())
    fun CharValue(value: Char) = CharValue(value, symbolTable())

    fun ASTNode.declaredType(): DataType = when (this) {
        is NavigationNode -> this.declaredType()
        is VariableReferenceNode -> this.declaredType()
        is IndexOpNode -> this.declaredType()
        else -> throw UnsupportedOperationException()
    }


    fun NavigationNode.declaredType(): DataType {
        return callStack.currentSymbolTable().typeNodeToPropertyType(type!!, false)!!.type
    }

    fun VariableReferenceNode.declaredType(): DataType {
        return callStack.currentSymbolTable().typeNodeToPropertyType(type!!, false)!!.type
    }

    fun IndexOpNode.declaredType(): DataType {
        return callStack.currentSymbolTable().assertToDataType(call!!.returnType!!)
    }

    fun eval(): RuntimeValue {
        log.d { "=== Interpreter eval() ===" }
        return rootNode.eval() as? RuntimeValue ?: UnitValue
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy