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

kotlinx.atomicfu.transformer.AtomicFUTransformerJS.kt Maven / Gradle / Ivy

/*
 * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.atomicfu.transformer

import org.mozilla.javascript.*
import org.mozilla.javascript.ast.*
import java.io.File
import java.io.FileReader
import org.mozilla.javascript.Token
import java.util.regex.*

private const val ATOMIC_CONSTRUCTOR = """(atomic\$(ref|int|long|boolean)\$|Atomic(Ref|Int|Long|Boolean))"""
private const val ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY = """(atomic\$(ref|int|long|boolean)\$1)""" // mangled names for declarations left for binary compatibility
private const val ATOMIC_ARRAY_CONSTRUCTOR = """(atomicfu)\$(Atomic(Ref|Int|Long|Boolean)Array)\$(ref|int|long|boolean|ofNulls)"""
private const val MANGLED_VALUE_PROP = "kotlinx\$atomicfu\$value"

private const val TRACE_CONSTRUCTOR = "atomicfu\\\$Trace"
private const val TRACE_BASE_CLASS = "atomicfu\\\$TraceBase"
private const val TRACE_APPEND = """(atomicfu)\$(Trace)\$(append)\$([1234])""" // [1234] is the number of arguments in the append overload
private const val TRACE_NAMED = "atomicfu\\\$Trace\\\$named"
private const val TRACE_FORMAT = "TraceFormat"
private const val TRACE_FORMAT_CONSTRUCTOR = "atomicfu\\\$$TRACE_FORMAT"
private const val TRACE_FORMAT_FORMAT = "atomicfu\\\$$TRACE_FORMAT\\\$format"

private const val RECEIVER = """(\$(receiver)(_\d+)?)"""
private const val SCOPE = "scope"
private const val FACTORY = "factory"
private const val REQUIRE = "require"
private const val PROTOTYPE = "prototype"
private const val KOTLINX_ATOMICFU = "'kotlinx-atomicfu'"
private const val KOTLINX_ATOMICFU_PACKAGE = "kotlinx.atomicfu"
private const val KOTLIN_TYPE_CHECK = "Kotlin.isType"
private const val ATOMIC_REF = "AtomicRef"
private const val MODULE_KOTLINX_ATOMICFU = "\\\$module\\\$kotlinx_atomicfu"
private const val ARRAY = "Array"
private const val FILL = "fill"
private const val GET_ELEMENT = "atomicfu\\\$get"
private const val ARRAY_SIZE = "atomicfu\$size"
private const val LENGTH = "length"
private const val LOCKS = "locks"
private const val REENTRANT_LOCK_ATOMICFU_SINGLETON = "$LOCKS.atomicfu\\\$reentrantLock"


private val MANGLE_VALUE_REGEX = Regex(".${Pattern.quote(MANGLED_VALUE_PROP)}")
// matches index until the first occurence of ')', parenthesised index expressions not supported
private val ARRAY_GET_ELEMENT_REGEX = Regex(".$GET_ELEMENT\\((.*)\\)")

class AtomicFUTransformerJS(
    inputDir: File,
    outputDir: File
) : AtomicFUTransformerBase(inputDir, outputDir) {
    private val atomicConstructors = mutableSetOf()
    private val delegateToOriginalAtomicField = mutableMapOf()
    private val topLevelDelegatedFieldAccessorToOriginalField = mutableMapOf()
    private val atomicArrayConstructors = mutableMapOf()
    private val traceConstructors = mutableSetOf()
    private val traceFormatObjects = mutableSetOf()

    override fun transform() {
        info("Transforming to $outputDir")
        inputDir.walk().filter { it.isFile }.forEach { file ->
            val outBytes = if (file.isJsFile()) {
                println("Transforming file: ${file.canonicalPath}")
                transformFile(file)
            } else {
                file.readBytes()
            }
            file.toOutputFile().mkdirsAndWrite(outBytes)
        }
    }

    private fun File.isJsFile() =
        name.endsWith(".js") && !name.endsWith(".meta.js")

    private fun transformFile(file: File): ByteArray {
        val p = Parser(CompilerEnvirons())
        val root = p.parse(FileReader(file), null, 0)
        root.visit(DependencyEraser())
        root.visit(AtomicConstructorDetector())
        root.visit(FieldDelegatesVisitor())
        root.visit(DelegatedPropertyAccessorsVisitor())
        root.visit(TopLevelDelegatedFieldsAccessorVisitor())
        root.visit(TransformVisitor())
        root.visit(AtomicOperationsInliner())
        return root.eraseGetValue().toByteArray()
    }

    // erase getting value of atomic field
    private fun AstNode.eraseGetValue(): String {
        var res = this.toSource()
        val primitiveGetValue = MANGLE_VALUE_REGEX
        val arrayGetElement = ARRAY_GET_ELEMENT_REGEX
        while (res.contains(arrayGetElement)) {
            res = res.replace(arrayGetElement) { matchResult ->
                val greedyToLastClosingParen = matchResult.groupValues[1]
                var balance = 1
                var indexEndPos = 0
                for (i in 0 until greedyToLastClosingParen.length) {
                    val c = greedyToLastClosingParen[i]
                    if (c == '(') balance++
                    if (c == ')') balance--
                    if (balance == 0) {
                        indexEndPos = i
                        break
                    }
                }
                val closingParen = indexEndPos == greedyToLastClosingParen.lastIndex
                if (balance == 1) {
                    "[$greedyToLastClosingParen]"
                } else {
                    "[${greedyToLastClosingParen.substring(0, indexEndPos)}]${greedyToLastClosingParen.substring(indexEndPos + 1)}${if (!closingParen) ")" else ""}"
                }
            }
        }
        return res.replace(primitiveGetValue) { "" }
    }

    inner class DependencyEraser : NodeVisitor {
        private fun isAtomicfuDependency(node: AstNode) =
            (node.type == Token.STRING && node.toSource() == KOTLINX_ATOMICFU)

        private fun isAtomicfuModule(node: AstNode) =
            (node.type == Token.NAME && node.toSource().matches(Regex(MODULE_KOTLINX_ATOMICFU)))

        override fun visit(node: AstNode): Boolean {
            when (node.type) {
                Token.ARRAYLIT -> {
                    // erasing 'kotlinx-atomicfu' from the list of defined dependencies
                    val elements = (node as ArrayLiteral).elements as MutableList
                    val it = elements.listIterator()
                    while (it.hasNext()) {
                        val arg = it.next()
                        if (isAtomicfuDependency(arg)) {
                            it.remove()
                        }
                    }
                }
                Token.FUNCTION -> {
                    if (node is FunctionNode) {
                        val it = node.params.listIterator()
                        while (it.hasNext()) {
                            // erasing 'kotlinx-atomicfu' module passed as parameter
                            if (isAtomicfuModule(it.next())) {
                                it.remove()
                            }
                        }
                    }
                }
                Token.CALL -> {
                    if (node is FunctionCall && node.target.toSource() == FACTORY) {
                        val it = node.arguments.listIterator()
                        while (it.hasNext()) {
                            val arg = it.next()
                            when (arg.type) {
                                Token.GETELEM -> {
                                    // erasing 'kotlinx-atomicfu' dependency as factory argument
                                    if (isAtomicfuDependency((arg as ElementGet).element)) {
                                        it.remove()
                                    }
                                }
                                Token.CALL -> {
                                    // erasing require of 'kotlinx-atomicfu' dependency
                                    if ((arg as FunctionCall).target.toSource() == REQUIRE) {
                                        if (isAtomicfuDependency(arg.arguments[0])) {
                                            it.remove()
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                Token.GETELEM -> {
                    if (isAtomicfuDependency((node as ElementGet).element)) {
                        val enclosingNode = node.parent
                        // erasing the check whether 'kotlinx-atomicfu' is defined
                        if (enclosingNode.type == Token.TYPEOF) {
                            if (enclosingNode.parent.parent.type == Token.IF) {
                                val ifStatement = enclosingNode.parent.parent as IfStatement
                                val falseKeyword = KeywordLiteral()
                                falseKeyword.type = Token.FALSE
                                ifStatement.condition = falseKeyword
                                val oneLineBlock = Block()
                                oneLineBlock.addStatement(EmptyLine())
                                ifStatement.thenPart = oneLineBlock
                            }
                        }

                    }
                }
                Token.BLOCK -> {
                    // erasing importsForInline for 'kotlinx-atomicfu'
                    for (stmt in node) {
                        if (stmt is ExpressionStatement) {
                            val expr = stmt.expression
                            if (expr is Assignment && expr.left is ElementGet) {
                                if (isAtomicfuDependency((expr.left as ElementGet).element)) {
                                    node.replaceChild(stmt, EmptyLine())
                                }
                            }
                        }
                    }
                }
            }
            return true
        }
    }

    inner class AtomicConstructorDetector : NodeVisitor {
        private fun kotlinxAtomicfuModuleName(name: String) = "$MODULE_KOTLINX_ATOMICFU.$KOTLINX_ATOMICFU_PACKAGE.$name"

        override fun visit(node: AstNode?): Boolean {
            if (node is Block) {
                for (stmt in node) {
                    if (stmt is VariableDeclaration) {
                        val varInit = stmt.variables[0] as VariableInitializer
                        if (varInit.initializer is PropertyGet) {
                            val initializer = varInit.initializer.toSource()
                            if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($ATOMIC_CONSTRUCTOR|$ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY)""")))) {
                                atomicConstructors.add(varInit.target.toSource())
                                node.replaceChild(stmt, EmptyLine())
                            } else if (initializer.matches(Regex(kotlinxAtomicfuModuleName(TRACE_CONSTRUCTOR)))) {
                                traceConstructors.add(varInit.target.toSource())
                                node.replaceChild(stmt, EmptyLine())
                            } else if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($LOCKS|$TRACE_FORMAT_CONSTRUCTOR|$TRACE_BASE_CLASS|$TRACE_NAMED)""")))) {
                                node.replaceChild(stmt, EmptyLine())
                            }
                        }
                    }
                }
            }
            if (node is PropertyGet && node.property.toSource().matches(Regex(TRACE_FORMAT_FORMAT))) {
                val target = node.target
                node.property = Name().also { it.identifier = "emptyProperty" }
                if (target is PropertyGet && target.property.toSource().matches(Regex(PROTOTYPE))) {
                    traceFormatObjects.add(target.target.toSource())
                }
            }
            if (node is VariableInitializer && node.initializer is PropertyGet) {
                val initializer = node.initializer.toSource()
                if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) {
                    node.initializer = null
                }
                if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($ATOMIC_CONSTRUCTOR|$ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY)""")))) {
                    atomicConstructors.add(node.target.toSource())
                    node.initializer = null
                }
                if (initializer.matches(Regex(kotlinxAtomicfuModuleName(ATOMIC_ARRAY_CONSTRUCTOR)))) {
                    val initialValue = when (initializer.substringAfterLast('$')) {
                        "int" -> "0"
                        "long" -> "0"
                        "boolean" -> "false"
                        else -> null
                    }
                    atomicArrayConstructors[node.target.toSource()] = initialValue
                    node.initializer = null
                }
                return false
            } else if (node is Assignment && node.right is PropertyGet) {
                val initializer = node.right.toSource()
                if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) {
                    node.right = Name().also { it.identifier = "null" }
                    return false
                }
            }
            return true
        }
    }

    inner class FieldDelegatesVisitor : NodeVisitor {
        override fun visit(node: AstNode?): Boolean {
            if (node is FunctionCall) {
                val functionName = node.target.toSource()
                if (atomicConstructors.contains(functionName)) {
                    if (node.parent is Assignment) {
                        val assignment = node.parent as Assignment
                        val atomicField = assignment.left
                        val constructorBlock = ((node.parent.parent as? ExpressionStatement)?.parent as? Block)
                                ?: abort("Incorrect tree structure of the constructor block initializing ${node.parent.toSource()}")
                        // check if there is a delegate field initialized by the reference to this atomic
                        for (stmt in constructorBlock) {
                            if (stmt is ExpressionStatement) {
                                if (stmt.expression is Assignment) {
                                    val delegateAssignment = stmt.expression as Assignment
                                    val initializer = delegateAssignment.right
                                    if (initializer.toSource() == atomicField.toSource()) {
                                        if (delegateAssignment.right is PropertyGet) { // initialization of a class field
                                            // delegate${owner_class} to original atomic field
                                            val delegateFieldName = (delegateAssignment.left as PropertyGet).property.toSource()
                                            val ownerClassName = constructorBlock.enclosingFunction.functionName.identifier
                                            delegateToOriginalAtomicField["$delegateFieldName\$$ownerClassName"] =
                                                    (atomicField as PropertyGet).property
                                        } else { // top-level delegated fields
                                            val delegateFieldName = delegateAssignment.left.toSource()
                                            delegateToOriginalAtomicField[delegateFieldName] = atomicField as Name
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return true
        }
    }

    inner class DelegatedPropertyAccessorsVisitor : NodeVisitor {
        override fun visit(node: AstNode?): Boolean {
            // find ObjectLiteral with accessors of the delegated field (get: FunctionNode, set: FunctionNode)
            // redirect getter/setter from generated delegate field to the original atomic field
            if (node is ObjectLiteral && node.parent is FunctionCall &&
                    ((node.elements.size == 2 && node.elements[1].left.toSource() == "get") ||
                            (node.elements.size == 3 && node.elements[1].left.toSource() == "get" && node.elements[2].left.toSource() == "set"))) {
                // check that these are accessors of the atomic delegate field (check only getter)
                if (node.elements[1].right is FunctionNode) {
                    val getter = node.elements[1].right as FunctionNode
                    if (getter.body.hasChildren() && getter.body.firstChild is ReturnStatement) {
                        val returnStmt = getter.body.firstChild as ReturnStatement
                        if (returnStmt.returnValue is PropertyGet && (returnStmt.returnValue as PropertyGet).property.toSource() == MANGLED_VALUE_PROP) {
                            val delegateField = ((returnStmt.returnValue as PropertyGet).target as PropertyGet).property.toSource()
                            val ownerClassName = ((node.parent as FunctionCall).arguments[0] as PropertyGet).target.toSource()
                            val key = "$delegateField\$$ownerClassName"
                            delegateToOriginalAtomicField[key]?.let { atomicField ->
                                // get() = a$delegate.value -> _a.value
                                getter.replaceAccessedField(true, atomicField)
                                if (node.elements.size == 3) {
                                    // set(v: T) { a$delegate.value = v } -> { _a.value = v }
                                    val setter = node.elements[2].right as FunctionNode
                                    setter.replaceAccessedField(false, atomicField)
                                }
                            }
                        }
                    }
                }
            }
            if (node is ObjectLiteral && node.parent is FunctionCall && ((node.elements.size == 1 && node.elements[0].left.toSource() == "get") ||
                            node.elements.size == 2 && node.elements[0].left.toSource() == "get" && node.elements[1].left.toSource() == "set")) {
                val parent = node.parent as FunctionCall
                if (parent.arguments.size == 3 && parent.arguments[1] is StringLiteral) {
                    val topLevelDelegatedFieldName = (parent.arguments[1] as StringLiteral).value
                    if (topLevelDelegatedFieldName in delegateToOriginalAtomicField) {
                        val originalAtomicFieldName = delegateToOriginalAtomicField[topLevelDelegatedFieldName]!!
                        val getterName = node.elements[0].right.toSource()
                        topLevelDelegatedFieldAccessorToOriginalField[getterName] = originalAtomicFieldName
                        if (node.elements.size == 2) {
                            val setterName = node.elements[1].right.toSource()
                            topLevelDelegatedFieldAccessorToOriginalField[setterName] = originalAtomicFieldName
                        }
                    }
                }
            }
            return true
        }
    }

    private fun FunctionNode.replaceAccessedField(isGetter: Boolean, newField: Name) {
        val propertyGet = if (isGetter) {
            (body.firstChild as ReturnStatement).returnValue as PropertyGet
        } else {
            ((body.firstChild as ExpressionStatement).expression as Assignment).left as PropertyGet
        }
        if (propertyGet.target is PropertyGet) { // class member
            (propertyGet.target as PropertyGet).property = newField
        } else { // top-level field
            propertyGet.target = newField
        }
    }

    inner class TopLevelDelegatedFieldsAccessorVisitor : NodeVisitor {
        override fun visit(node: AstNode?): Boolean {
            if (node is FunctionNode && node.name.toString() in topLevelDelegatedFieldAccessorToOriginalField) {
                val accessorName = node.name.toString()
                val atomicField = topLevelDelegatedFieldAccessorToOriginalField[accessorName]!!
                // function get_topLevelDelegatedField() = a.value  -> _a.value
                // function set_topLevelDelegatedField(v: T) { a.value = v }  -> { _a.value = v }
                node.replaceAccessedField(accessorName.startsWith("get"), atomicField)
            }
            return true
        }
    }

    inner class TransformVisitor : NodeVisitor {
        override fun visit(node: AstNode): Boolean {
            // remove atomic constructors from classes fields
            if (node is FunctionCall) {
                val functionName = node.target.toSource()
                if (atomicConstructors.contains(functionName)) {
                    if (node.parent is Assignment) {
                        val valueNode = node.arguments[0]
                        (node.parent as Assignment).right = valueNode
                    }
                    return true
                } else if (atomicArrayConstructors.contains(functionName)) {
                    val arrayConstructor = Name()
                    arrayConstructor.identifier = ARRAY
                    node.target = arrayConstructor
                    atomicArrayConstructors[functionName]?.let {
                        val arrayConsCall = FunctionCall()
                        arrayConsCall.target = node.target
                        arrayConsCall.arguments = node.arguments
                        val target = PropertyGet()
                        val fill = Name()
                        fill.identifier = FILL
                        target.target = arrayConsCall
                        target.property = fill
                        node.target = target
                        val initialValue = Name()
                        initialValue.identifier = it
                        node.arguments = listOf(initialValue)
                    }
                    return true
                } else if (node.target is PropertyGet) {
                    if ((node.target as PropertyGet).target is FunctionCall) {
                        val atomicOperationTarget = node.target as PropertyGet
                        val funcCall = atomicOperationTarget.target as FunctionCall
                        if (funcCall.target is PropertyGet) {
                            val getterCall = (funcCall.target as PropertyGet).property
                            if (Regex(GET_ELEMENT).matches(getterCall.toSource())) {
                                val getter = getArrayElement(funcCall)
                                atomicOperationTarget.target = getter
                            }
                        }
                    }
                }
            }
            // remove value property call
            if (node is PropertyGet) {
                if (node.property.toSource() == MANGLED_VALUE_PROP) {
                    // check whether atomic operation is performed on the type casted atomic field
                    node.target.eraseAtomicFieldFromUncheckedCast()?.let { node.target = it }
                    // A.a.value
                    if (node.target.type == Token.GETPROP) {
                        val clearField = node.target as PropertyGet
                        val targetNode = clearField.target
                        val clearProperety = clearField.property
                        node.setLeftAndRight(targetNode, clearProperety)
                    }
                    // other cases with $receiver.kotlinx$atomicfu$value in inline functions
                    else if (node.target.toSource().matches(Regex(RECEIVER))) {
                        val receiverName = node.target.toSource() // $receiver_i
                        val rr = ReceiverResolver(receiverName)
                        node.enclosingFunction.visit(rr)
                        rr.receiver?.let { node.target = it }
                    }
                }
                // replace Atomic*Array.size call with `length` property on the pure type js array
                if (node.property.toSource() == ARRAY_SIZE) {
                    node.property = Name().also { it.identifier = LENGTH }
                }
            }
            if (node is Block) {
                for (stmt in node) {
                    if (stmt is ExpressionStatement) {
                        if (stmt.expression is Assignment) {
                            // erase field initialisation
                            val assignment = stmt.expression as Assignment
                            if (assignment.right is FunctionCall) {
                                val functionName = (assignment.right as FunctionCall).target.toSource()
                                if (traceConstructors.contains(functionName)) {
                                    node.replaceChild(stmt, EmptyLine())
                                }
                            }
                        }
                        if (stmt.expression is FunctionCall) {
                            // erase append(text) call
                            val funcNode = (stmt.expression as FunctionCall).target
                            if (funcNode is PropertyGet && funcNode.property.toSource().matches(Regex(TRACE_APPEND))) {
                                node.replaceChild(stmt, EmptyLine())
                            }
                        }
                    }
                }
            }
            if (node is Assignment && node.left is PropertyGet) {
                val left = node.left as PropertyGet
                if (traceFormatObjects.contains(left.target.toSource())) {
                    if (node.right is FunctionCall) {
                        // TraceFormatObject initialization
                        (node.right as FunctionCall).arguments = listOf(Name().also { it.identifier = "null" })
                    }
                }
            }
            // remove TraceFormatObject constructor definition
            if (node is FunctionNode && traceFormatObjects.contains(node.name)) {
                val body  = node.body
                for (stmt in body) { body.replaceChild(stmt, EmptyLine()) }
            }
            // remove TraceFormat from TraceFormatObject interfaces
            if (node is Assignment && node.left is PropertyGet && node.right is ObjectLiteral) {
                val left = node.left as PropertyGet
                val metadata = node.right as ObjectLiteral
                if (traceFormatObjects.contains(left.target.toSource())) {
                    for (e in metadata.elements) {
                        if (e.right is ArrayLiteral) {
                            val array = (e.right as ArrayLiteral).toSource()
                            if (array.contains(TRACE_FORMAT)) {
                                (e.right as ArrayLiteral).elements = emptyList()
                            }
                        }
                    }
                }
            }
            return true
        }

        private fun getArrayElement(getterCall: FunctionCall): AstNode {
            val index = getterCall.arguments[0]
            val arrayField = (getterCall.target as PropertyGet).target
            // whether this field is static or not
            val isStatic = arrayField !is PropertyGet
            val arrName = if (isStatic) arrayField else (arrayField as PropertyGet).property
            val getter = ElementGet(arrName, index)
            return if (isStatic) { //intArr[index]
                getter
            } else { //A.intArr[0]
                val call = PropertyGet()
                call.target = (arrayField as PropertyGet).target
                val name = Name()
                name.identifier = getter.toSource()
                call.property = name
                call
            }
        }
    }


    // receiver data flow
    inner class ReceiverResolver(private val receiverName: String) : NodeVisitor {
        var receiver: AstNode? = null
        override fun visit(node: AstNode): Boolean {
            if (node is VariableInitializer) {
                if (node.target.toSource() == receiverName) {
                    receiver = node.initializer
                    return false
                }
            }
            return true
        }
    }

    inner class AtomicOperationsInliner : NodeVisitor {
        override fun visit(node: AstNode?): Boolean {
            // inline atomic operations
            if (node is FunctionCall) {
                if (node.target is PropertyGet) {
                    val funcName = (node.target as PropertyGet).property
                    var field = (node.target as PropertyGet).target
                    if (field.toSource().matches(Regex(RECEIVER))) {
                        val receiverName = field.toSource() // $receiver_i
                        val rr = ReceiverResolver(receiverName)
                        node.enclosingFunction.visit(rr)
                        if (rr.receiver != null) {
                            field = rr.receiver
                        }
                    }
                    field.eraseAtomicFieldFromUncheckedCast()?.let { field = it }
                    val args = node.arguments
                    val inlined = node.inlineAtomicOperation(funcName.toSource(), field, args)
                    return !inlined
                }
            }
            return true
        }
    }

    private fun AstNode.eraseAtomicFieldFromUncheckedCast(): AstNode? {
        if (this is ParenthesizedExpression && expression is ConditionalExpression) {
            val testExpression = (expression as ConditionalExpression).testExpression
            if (testExpression is FunctionCall && testExpression.target.toSource() == KOTLIN_TYPE_CHECK) {
                // type check
                val typeToCast = testExpression.arguments[1]
                if ((typeToCast as Name).identifier == ATOMIC_REF) {
                    // unchecked type cast -> erase atomic field itself
                    return (testExpression.arguments[0] as Assignment).right
                }
            }
        }
        return null
    }

    private fun AstNode.isThisNode(): Boolean {
        return when(this) {
            is PropertyGet -> {
                target.isThisNode()
            }
            is FunctionCall -> {
                target.isThisNode()
            }
            else -> {
                (this.type == Token.THIS)
            }
        }
    }

    private fun PropertyGet.resolvePropName(): String {
        val target = this.target
        return if (target is PropertyGet) {
            "${target.resolvePropName()}.${property.toSource()}"
        } else {
            property.toSource()
        }
    }

    private fun AstNode.scopedSource(): String {
        if (this.isThisNode()) {
            if (this is PropertyGet) {
                val property = resolvePropName()
                return "$SCOPE.$property"
            } else if (this is FunctionCall && this.target is PropertyGet) {
                // check that this function call is getting array element
                if (this.target is PropertyGet) {
                    val funcName = (this.target as PropertyGet).property.toSource()
                    if (Regex(GET_ELEMENT).matches(funcName)) {
                        val property = (this.target as PropertyGet).resolvePropName()
                        return "$SCOPE.$property(${this.arguments[0].toSource()})"
                    }
                }
            } else if (this.type == Token.THIS) {
                return SCOPE
            }
        }
        return this.toSource()
    }

    private fun FunctionCall.inlineAtomicOperation(
        funcName: String,
        field: AstNode,
        args: List
    ): Boolean {
        val f = field.scopedSource()
        val code = when (funcName) {
            "atomicfu\$getAndSet" -> {
                val arg = args[0].toSource()
                "(function($SCOPE) {var oldValue = $f; $f = $arg; return oldValue;})()"
            }
            "atomicfu\$compareAndSet" -> {
                val expected = args[0].scopedSource()
                val updated = args[1].scopedSource()
                val equals = if (expected == "null") "==" else "==="
                "(function($SCOPE) {return $f $equals $expected ? function() { $f = $updated; return true }() : false})()"
            }
            "atomicfu\$getAndIncrement" -> {
                "(function($SCOPE) {return $f++;})()"
            }

            "atomicfu\$getAndIncrement\$long" -> {
                "(function($SCOPE) {var oldValue = $f; $f = $f.inc(); return oldValue;})()"
            }

            "atomicfu\$getAndDecrement" -> {
                "(function($SCOPE) {return $f--;})()"
            }

            "atomicfu\$getAndDecrement\$long" -> {
                "(function($SCOPE) {var oldValue = $f; $f = $f.dec(); return oldValue;})()"
            }

            "atomicfu\$getAndAdd" -> {
                val arg = args[0].scopedSource()
                "(function($SCOPE) {var oldValue = $f; $f += $arg; return oldValue;})()"
            }

            "atomicfu\$getAndAdd\$long" -> {
                val arg = args[0].scopedSource()
                "(function($SCOPE) {var oldValue = $f; $f = $f.add($arg); return oldValue;})()"
            }

            "atomicfu\$addAndGet" -> {
                val arg = args[0].scopedSource()
                "(function($SCOPE) {$f += $arg; return $f;})()"
            }

            "atomicfu\$addAndGet\$long" -> {
                val arg = args[0].scopedSource()
                "(function($SCOPE) {$f = $f.add($arg); return $f;})()"
            }

            "atomicfu\$incrementAndGet" -> {
                "(function($SCOPE) {return ++$f;})()"
            }

            "atomicfu\$incrementAndGet\$long" -> {
                "(function($SCOPE) {return $f = $f.inc();})()"
            }

            "atomicfu\$decrementAndGet" -> {
                "(function($SCOPE) {return --$f;})()"
            }

            "atomicfu\$decrementAndGet\$long" -> {
                "(function($SCOPE) {return $f = $f.dec();})()"
            }
            else -> null
        }
        if (code != null) {
            this.setImpl(code)
            return true
        }
        return false
    }

    private fun FunctionCall.setImpl(code: String) {
        val p = Parser(CompilerEnvirons())
        val node = p.parse(code, null, 0)
        if (node.firstChild != null) {
            val expr = (node.firstChild as ExpressionStatement).expression
            this.target = (expr as FunctionCall).target
            val thisNode = Parser(CompilerEnvirons()).parse("this", null, 0)
            this.arguments = listOf((thisNode.firstChild as ExpressionStatement).expression)
        }
    }
}

private class EmptyLine : EmptyExpression() {
    override fun toSource(depth: Int) = "\n"
}

fun main(args: Array) {
    if (args.size !in 1..2) {
        println("Usage: AtomicFUTransformerKt  []")
        return
    }
    val t = AtomicFUTransformerJS(File(args[0]), File(args[1]))
    t.transform()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy