1 /*
<lambda>null2  * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.atomicfu.transformer
6 
7 import org.mozilla.javascript.*
8 import org.mozilla.javascript.ast.*
9 import java.io.File
10 import java.io.FileReader
11 import org.mozilla.javascript.Token
12 import java.util.regex.*
13 
14 private const val ATOMIC_CONSTRUCTOR = """(atomic\$(ref|int|long|boolean)\$|Atomic(Ref|Int|Long|Boolean))"""
15 private const val ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY = """(atomic\$(ref|int|long|boolean)\$1)""" // mangled names for declarations left for binary compatibility
16 private const val ATOMIC_ARRAY_CONSTRUCTOR = """(atomicfu)\$(Atomic(Ref|Int|Long|Boolean)Array)\$(ref|int|long|boolean|ofNulls)"""
17 private const val MANGLED_VALUE_PROP = "kotlinx\$atomicfu\$value"
18 
19 private const val TRACE_CONSTRUCTOR = "atomicfu\\\$Trace"
20 private const val TRACE_BASE_CLASS = "atomicfu\\\$TraceBase"
21 private const val TRACE_APPEND = """(atomicfu)\$(Trace)\$(append)\$([1234])""" // [1234] is the number of arguments in the append overload
22 private const val TRACE_NAMED = "atomicfu\\\$Trace\\\$named"
23 private const val TRACE_FORMAT = "TraceFormat"
24 private const val TRACE_FORMAT_CONSTRUCTOR = "atomicfu\\\$$TRACE_FORMAT"
25 private const val TRACE_FORMAT_FORMAT = "atomicfu\\\$$TRACE_FORMAT\\\$format"
26 
27 private const val RECEIVER = """(\$(receiver)(_\d+)?)"""
28 private const val SCOPE = "scope"
29 private const val FACTORY = "factory"
30 private const val REQUIRE = "require"
31 private const val PROTOTYPE = "prototype"
32 private const val KOTLINX_ATOMICFU = "'kotlinx-atomicfu'"
33 private const val KOTLINX_ATOMICFU_PACKAGE = "kotlinx.atomicfu"
34 private const val KOTLIN_TYPE_CHECK = "Kotlin.isType"
35 private const val ATOMIC_REF = "AtomicRef"
36 private const val MODULE_KOTLINX_ATOMICFU = "\\\$module\\\$kotlinx_atomicfu"
37 private const val ARRAY = "Array"
38 private const val FILL = "fill"
39 private const val GET_ELEMENT = "atomicfu\\\$get"
40 private const val ARRAY_SIZE = "atomicfu\$size"
41 private const val LENGTH = "length"
42 private const val LOCKS = "locks"
43 private const val REENTRANT_LOCK_ATOMICFU_SINGLETON = "$LOCKS.atomicfu\\\$reentrantLock"
44 
45 
46 private val MANGLE_VALUE_REGEX = Regex(".${Pattern.quote(MANGLED_VALUE_PROP)}")
47 // matches index until the first occurence of ')', parenthesised index expressions not supported
48 private val ARRAY_GET_ELEMENT_REGEX = Regex(".$GET_ELEMENT\\((.*)\\)")
49 
50 class AtomicFUTransformerJS(
51     inputDir: File,
52     outputDir: File
53 ) : AtomicFUTransformerBase(inputDir, outputDir) {
54     private val atomicConstructors = mutableSetOf<String>()
55     private val delegateToOriginalAtomicField = mutableMapOf<String, Name>()
56     private val topLevelDelegatedFieldAccessorToOriginalField = mutableMapOf<String, Name>()
57     private val atomicArrayConstructors = mutableMapOf<String, String?>()
58     private val traceConstructors = mutableSetOf<String>()
59     private val traceFormatObjects = mutableSetOf<String>()
60 
61     override fun transform() {
62         info("Transforming to $outputDir")
63         inputDir.walk().filter { it.isFile }.forEach { file ->
64             val outBytes = if (file.isJsFile()) {
65                 println("Transforming file: ${file.canonicalPath}")
66                 transformFile(file)
67             } else {
68                 file.readBytes()
69             }
70             file.toOutputFile().mkdirsAndWrite(outBytes)
71         }
72     }
73 
74     private fun File.isJsFile() =
75         name.endsWith(".js") && !name.endsWith(".meta.js")
76 
77     private fun transformFile(file: File): ByteArray {
78         val p = Parser(CompilerEnvirons())
79         val root = p.parse(FileReader(file), null, 0)
80         root.visit(DependencyEraser())
81         root.visit(AtomicConstructorDetector())
82         root.visit(FieldDelegatesVisitor())
83         root.visit(DelegatedPropertyAccessorsVisitor())
84         root.visit(TopLevelDelegatedFieldsAccessorVisitor())
85         root.visit(TransformVisitor())
86         root.visit(AtomicOperationsInliner())
87         return root.eraseGetValue().toByteArray()
88     }
89 
90     // erase getting value of atomic field
91     private fun AstNode.eraseGetValue(): String {
92         var res = this.toSource()
93         val primitiveGetValue = MANGLE_VALUE_REGEX
94         val arrayGetElement = ARRAY_GET_ELEMENT_REGEX
95         while (res.contains(arrayGetElement)) {
96             res = res.replace(arrayGetElement) { matchResult ->
97                 val greedyToLastClosingParen = matchResult.groupValues[1]
98                 var balance = 1
99                 var indexEndPos = 0
100                 for (i in 0 until greedyToLastClosingParen.length) {
101                     val c = greedyToLastClosingParen[i]
102                     if (c == '(') balance++
103                     if (c == ')') balance--
104                     if (balance == 0) {
105                         indexEndPos = i
106                         break
107                     }
108                 }
109                 val closingParen = indexEndPos == greedyToLastClosingParen.lastIndex
110                 if (balance == 1) {
111                     "[$greedyToLastClosingParen]"
112                 } else {
113                     "[${greedyToLastClosingParen.substring(0, indexEndPos)}]${greedyToLastClosingParen.substring(indexEndPos + 1)}${if (!closingParen) ")" else ""}"
114                 }
115             }
116         }
117         return res.replace(primitiveGetValue) { "" }
118     }
119 
120     inner class DependencyEraser : NodeVisitor {
121         private fun isAtomicfuDependency(node: AstNode) =
122             (node.type == Token.STRING && node.toSource() == KOTLINX_ATOMICFU)
123 
124         private fun isAtomicfuModule(node: AstNode) =
125             (node.type == Token.NAME && node.toSource().matches(Regex(MODULE_KOTLINX_ATOMICFU)))
126 
127         override fun visit(node: AstNode): Boolean {
128             when (node.type) {
129                 Token.ARRAYLIT -> {
130                     // erasing 'kotlinx-atomicfu' from the list of defined dependencies
131                     val elements = (node as ArrayLiteral).elements as MutableList
132                     val it = elements.listIterator()
133                     while (it.hasNext()) {
134                         val arg = it.next()
135                         if (isAtomicfuDependency(arg)) {
136                             it.remove()
137                         }
138                     }
139                 }
140                 Token.FUNCTION -> {
141                     if (node is FunctionNode) {
142                         val it = node.params.listIterator()
143                         while (it.hasNext()) {
144                             // erasing 'kotlinx-atomicfu' module passed as parameter
145                             if (isAtomicfuModule(it.next())) {
146                                 it.remove()
147                             }
148                         }
149                     }
150                 }
151                 Token.CALL -> {
152                     if (node is FunctionCall && node.target.toSource() == FACTORY) {
153                         val it = node.arguments.listIterator()
154                         while (it.hasNext()) {
155                             val arg = it.next()
156                             when (arg.type) {
157                                 Token.GETELEM -> {
158                                     // erasing 'kotlinx-atomicfu' dependency as factory argument
159                                     if (isAtomicfuDependency((arg as ElementGet).element)) {
160                                         it.remove()
161                                     }
162                                 }
163                                 Token.CALL -> {
164                                     // erasing require of 'kotlinx-atomicfu' dependency
165                                     if ((arg as FunctionCall).target.toSource() == REQUIRE) {
166                                         if (isAtomicfuDependency(arg.arguments[0])) {
167                                             it.remove()
168                                         }
169                                     }
170                                 }
171                             }
172                         }
173                     }
174                 }
175                 Token.GETELEM -> {
176                     if (isAtomicfuDependency((node as ElementGet).element)) {
177                         val enclosingNode = node.parent
178                         // erasing the check whether 'kotlinx-atomicfu' is defined
179                         if (enclosingNode.type == Token.TYPEOF) {
180                             if (enclosingNode.parent.parent.type == Token.IF) {
181                                 val ifStatement = enclosingNode.parent.parent as IfStatement
182                                 val falseKeyword = KeywordLiteral()
183                                 falseKeyword.type = Token.FALSE
184                                 ifStatement.condition = falseKeyword
185                                 val oneLineBlock = Block()
186                                 oneLineBlock.addStatement(EmptyLine())
187                                 ifStatement.thenPart = oneLineBlock
188                             }
189                         }
190 
191                     }
192                 }
193                 Token.BLOCK -> {
194                     // erasing importsForInline for 'kotlinx-atomicfu'
195                     for (stmt in node) {
196                         if (stmt is ExpressionStatement) {
197                             val expr = stmt.expression
198                             if (expr is Assignment && expr.left is ElementGet) {
199                                 if (isAtomicfuDependency((expr.left as ElementGet).element)) {
200                                     node.replaceChild(stmt, EmptyLine())
201                                 }
202                             }
203                         }
204                     }
205                 }
206             }
207             return true
208         }
209     }
210 
211     inner class AtomicConstructorDetector : NodeVisitor {
212         private fun kotlinxAtomicfuModuleName(name: String) = "$MODULE_KOTLINX_ATOMICFU.$KOTLINX_ATOMICFU_PACKAGE.$name"
213 
214         override fun visit(node: AstNode?): Boolean {
215             if (node is Block) {
216                 for (stmt in node) {
217                     if (stmt is VariableDeclaration) {
218                         val varInit = stmt.variables[0] as VariableInitializer
219                         if (varInit.initializer is PropertyGet) {
220                             val initializer = varInit.initializer.toSource()
221                             if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($ATOMIC_CONSTRUCTOR|$ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY)""")))) {
222                                 atomicConstructors.add(varInit.target.toSource())
223                                 node.replaceChild(stmt, EmptyLine())
224                             } else if (initializer.matches(Regex(kotlinxAtomicfuModuleName(TRACE_CONSTRUCTOR)))) {
225                                 traceConstructors.add(varInit.target.toSource())
226                                 node.replaceChild(stmt, EmptyLine())
227                             } else if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($LOCKS|$TRACE_FORMAT_CONSTRUCTOR|$TRACE_BASE_CLASS|$TRACE_NAMED)""")))) {
228                                 node.replaceChild(stmt, EmptyLine())
229                             }
230                         }
231                     }
232                 }
233             }
234             if (node is PropertyGet && node.property.toSource().matches(Regex(TRACE_FORMAT_FORMAT))) {
235                 val target = node.target
236                 node.property = Name().also { it.identifier = "emptyProperty" }
237                 if (target is PropertyGet && target.property.toSource().matches(Regex(PROTOTYPE))) {
238                     traceFormatObjects.add(target.target.toSource())
239                 }
240             }
241             if (node is VariableInitializer && node.initializer is PropertyGet) {
242                 val initializer = node.initializer.toSource()
243                 if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) {
244                     node.initializer = null
245                 }
246                 if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($ATOMIC_CONSTRUCTOR|$ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY)""")))) {
247                     atomicConstructors.add(node.target.toSource())
248                     node.initializer = null
249                 }
250                 if (initializer.matches(Regex(kotlinxAtomicfuModuleName(ATOMIC_ARRAY_CONSTRUCTOR)))) {
251                     val initialValue = when (initializer.substringAfterLast('$')) {
252                         "int" -> "0"
253                         "long" -> "0"
254                         "boolean" -> "false"
255                         else -> null
256                     }
257                     atomicArrayConstructors[node.target.toSource()] = initialValue
258                     node.initializer = null
259                 }
260                 return false
261             } else if (node is Assignment && node.right is PropertyGet) {
262                 val initializer = node.right.toSource()
263                 if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) {
264                     node.right = Name().also { it.identifier = "null" }
265                     return false
266                 }
267             }
268             return true
269         }
270     }
271 
272     inner class FieldDelegatesVisitor : NodeVisitor {
273         override fun visit(node: AstNode?): Boolean {
274             if (node is FunctionCall) {
275                 val functionName = node.target.toSource()
276                 if (atomicConstructors.contains(functionName)) {
277                     if (node.parent is Assignment) {
278                         val assignment = node.parent as Assignment
279                         val atomicField = assignment.left
280                         val constructorBlock = ((node.parent.parent as? ExpressionStatement)?.parent as? Block)
281                                 ?: abort("Incorrect tree structure of the constructor block initializing ${node.parent.toSource()}")
282                         // check if there is a delegate field initialized by the reference to this atomic
283                         for (stmt in constructorBlock) {
284                             if (stmt is ExpressionStatement) {
285                                 if (stmt.expression is Assignment) {
286                                     val delegateAssignment = stmt.expression as Assignment
287                                     val initializer = delegateAssignment.right
288                                     if (initializer.toSource() == atomicField.toSource()) {
289                                         if (delegateAssignment.right is PropertyGet) { // initialization of a class field
290                                             // delegate${owner_class} to original atomic field
291                                             val delegateFieldName = (delegateAssignment.left as PropertyGet).property.toSource()
292                                             val ownerClassName = constructorBlock.enclosingFunction.functionName.identifier
293                                             delegateToOriginalAtomicField["$delegateFieldName\$$ownerClassName"] =
294                                                     (atomicField as PropertyGet).property
295                                         } else { // top-level delegated fields
296                                             val delegateFieldName = delegateAssignment.left.toSource()
297                                             delegateToOriginalAtomicField[delegateFieldName] = atomicField as Name
298                                         }
299                                     }
300                                 }
301                             }
302                         }
303                     }
304                 }
305             }
306             return true
307         }
308     }
309 
310     inner class DelegatedPropertyAccessorsVisitor : NodeVisitor {
311         override fun visit(node: AstNode?): Boolean {
312             // find ObjectLiteral with accessors of the delegated field (get: FunctionNode, set: FunctionNode)
313             // redirect getter/setter from generated delegate field to the original atomic field
314             if (node is ObjectLiteral && node.parent is FunctionCall &&
315                     ((node.elements.size == 2 && node.elements[1].left.toSource() == "get") ||
316                             (node.elements.size == 3 && node.elements[1].left.toSource() == "get" && node.elements[2].left.toSource() == "set"))) {
317                 // check that these are accessors of the atomic delegate field (check only getter)
318                 if (node.elements[1].right is FunctionNode) {
319                     val getter = node.elements[1].right as FunctionNode
320                     if (getter.body.hasChildren() && getter.body.firstChild is ReturnStatement) {
321                         val returnStmt = getter.body.firstChild as ReturnStatement
322                         if (returnStmt.returnValue is PropertyGet && (returnStmt.returnValue as PropertyGet).property.toSource() == MANGLED_VALUE_PROP) {
323                             val delegateField = ((returnStmt.returnValue as PropertyGet).target as PropertyGet).property.toSource()
324                             val ownerClassName = ((node.parent as FunctionCall).arguments[0] as PropertyGet).target.toSource()
325                             val key = "$delegateField\$$ownerClassName"
326                             delegateToOriginalAtomicField[key]?.let { atomicField ->
327                                 // get() = a$delegate.value -> _a.value
328                                 getter.replaceAccessedField(true, atomicField)
329                                 if (node.elements.size == 3) {
330                                     // set(v: T) { a$delegate.value = v } -> { _a.value = v }
331                                     val setter = node.elements[2].right as FunctionNode
332                                     setter.replaceAccessedField(false, atomicField)
333                                 }
334                             }
335                         }
336                     }
337                 }
338             }
339             if (node is ObjectLiteral && node.parent is FunctionCall && ((node.elements.size == 1 && node.elements[0].left.toSource() == "get") ||
340                             node.elements.size == 2 && node.elements[0].left.toSource() == "get" && node.elements[1].left.toSource() == "set")) {
341                 val parent = node.parent as FunctionCall
342                 if (parent.arguments.size == 3 && parent.arguments[1] is StringLiteral) {
343                     val topLevelDelegatedFieldName = (parent.arguments[1] as StringLiteral).value
344                     if (topLevelDelegatedFieldName in delegateToOriginalAtomicField) {
345                         val originalAtomicFieldName = delegateToOriginalAtomicField[topLevelDelegatedFieldName]!!
346                         val getterName = node.elements[0].right.toSource()
347                         topLevelDelegatedFieldAccessorToOriginalField[getterName] = originalAtomicFieldName
348                         if (node.elements.size == 2) {
349                             val setterName = node.elements[1].right.toSource()
350                             topLevelDelegatedFieldAccessorToOriginalField[setterName] = originalAtomicFieldName
351                         }
352                     }
353                 }
354             }
355             return true
356         }
357     }
358 
359     private fun FunctionNode.replaceAccessedField(isGetter: Boolean, newField: Name) {
360         val propertyGet = if (isGetter) {
361             (body.firstChild as ReturnStatement).returnValue as PropertyGet
362         } else {
363             ((body.firstChild as ExpressionStatement).expression as Assignment).left as PropertyGet
364         }
365         if (propertyGet.target is PropertyGet) { // class member
366             (propertyGet.target as PropertyGet).property = newField
367         } else { // top-level field
368             propertyGet.target = newField
369         }
370     }
371 
372     inner class TopLevelDelegatedFieldsAccessorVisitor : NodeVisitor {
373         override fun visit(node: AstNode?): Boolean {
374             if (node is FunctionNode && node.name.toString() in topLevelDelegatedFieldAccessorToOriginalField) {
375                 val accessorName = node.name.toString()
376                 val atomicField = topLevelDelegatedFieldAccessorToOriginalField[accessorName]!!
377                 // function get_topLevelDelegatedField() = a.value  -> _a.value
378                 // function set_topLevelDelegatedField(v: T) { a.value = v }  -> { _a.value = v }
379                 node.replaceAccessedField(accessorName.startsWith("get"), atomicField)
380             }
381             return true
382         }
383     }
384 
385     inner class TransformVisitor : NodeVisitor {
386         override fun visit(node: AstNode): Boolean {
387             // remove atomic constructors from classes fields
388             if (node is FunctionCall) {
389                 val functionName = node.target.toSource()
390                 if (atomicConstructors.contains(functionName)) {
391                     if (node.parent is Assignment) {
392                         val valueNode = node.arguments[0]
393                         (node.parent as Assignment).right = valueNode
394                     }
395                     return true
396                 } else if (atomicArrayConstructors.contains(functionName)) {
397                     val arrayConstructor = Name()
398                     arrayConstructor.identifier = ARRAY
399                     node.target = arrayConstructor
400                     atomicArrayConstructors[functionName]?.let {
401                         val arrayConsCall = FunctionCall()
402                         arrayConsCall.target = node.target
403                         arrayConsCall.arguments = node.arguments
404                         val target = PropertyGet()
405                         val fill = Name()
406                         fill.identifier = FILL
407                         target.target = arrayConsCall
408                         target.property = fill
409                         node.target = target
410                         val initialValue = Name()
411                         initialValue.identifier = it
412                         node.arguments = listOf(initialValue)
413                     }
414                     return true
415                 } else if (node.target is PropertyGet) {
416                     if ((node.target as PropertyGet).target is FunctionCall) {
417                         val atomicOperationTarget = node.target as PropertyGet
418                         val funcCall = atomicOperationTarget.target as FunctionCall
419                         if (funcCall.target is PropertyGet) {
420                             val getterCall = (funcCall.target as PropertyGet).property
421                             if (Regex(GET_ELEMENT).matches(getterCall.toSource())) {
422                                 val getter = getArrayElement(funcCall)
423                                 atomicOperationTarget.target = getter
424                             }
425                         }
426                     }
427                 }
428             }
429             // remove value property call
430             if (node is PropertyGet) {
431                 if (node.property.toSource() == MANGLED_VALUE_PROP) {
432                     // check whether atomic operation is performed on the type casted atomic field
433                     node.target.eraseAtomicFieldFromUncheckedCast()?.let { node.target = it }
434                     // A.a.value
435                     if (node.target.type == Token.GETPROP) {
436                         val clearField = node.target as PropertyGet
437                         val targetNode = clearField.target
438                         val clearProperety = clearField.property
439                         node.setLeftAndRight(targetNode, clearProperety)
440                     }
441                     // other cases with $receiver.kotlinx$atomicfu$value in inline functions
442                     else if (node.target.toSource().matches(Regex(RECEIVER))) {
443                         val receiverName = node.target.toSource() // $receiver_i
444                         val rr = ReceiverResolver(receiverName)
445                         node.enclosingFunction.visit(rr)
446                         rr.receiver?.let { node.target = it }
447                     }
448                 }
449                 // replace Atomic*Array.size call with `length` property on the pure type js array
450                 if (node.property.toSource() == ARRAY_SIZE) {
451                     node.property = Name().also { it.identifier = LENGTH }
452                 }
453             }
454             if (node is Block) {
455                 for (stmt in node) {
456                     if (stmt is ExpressionStatement) {
457                         if (stmt.expression is Assignment) {
458                             // erase field initialisation
459                             val assignment = stmt.expression as Assignment
460                             if (assignment.right is FunctionCall) {
461                                 val functionName = (assignment.right as FunctionCall).target.toSource()
462                                 if (traceConstructors.contains(functionName)) {
463                                     node.replaceChild(stmt, EmptyLine())
464                                 }
465                             }
466                         }
467                         if (stmt.expression is FunctionCall) {
468                             // erase append(text) call
469                             val funcNode = (stmt.expression as FunctionCall).target
470                             if (funcNode is PropertyGet && funcNode.property.toSource().matches(Regex(TRACE_APPEND))) {
471                                 node.replaceChild(stmt, EmptyLine())
472                             }
473                         }
474                     }
475                 }
476             }
477             if (node is Assignment && node.left is PropertyGet) {
478                 val left = node.left as PropertyGet
479                 if (traceFormatObjects.contains(left.target.toSource())) {
480                     if (node.right is FunctionCall) {
481                         // TraceFormatObject initialization
482                         (node.right as FunctionCall).arguments = listOf(Name().also { it.identifier = "null" })
483                     }
484                 }
485             }
486             // remove TraceFormatObject constructor definition
487             if (node is FunctionNode && traceFormatObjects.contains(node.name)) {
488                 val body  = node.body
489                 for (stmt in body) { body.replaceChild(stmt, EmptyLine()) }
490             }
491             // remove TraceFormat from TraceFormatObject interfaces
492             if (node is Assignment && node.left is PropertyGet && node.right is ObjectLiteral) {
493                 val left = node.left as PropertyGet
494                 val metadata = node.right as ObjectLiteral
495                 if (traceFormatObjects.contains(left.target.toSource())) {
496                     for (e in metadata.elements) {
497                         if (e.right is ArrayLiteral) {
498                             val array = (e.right as ArrayLiteral).toSource()
499                             if (array.contains(TRACE_FORMAT)) {
500                                 (e.right as ArrayLiteral).elements = emptyList()
501                             }
502                         }
503                     }
504                 }
505             }
506             return true
507         }
508 
509         private fun getArrayElement(getterCall: FunctionCall): AstNode {
510             val index = getterCall.arguments[0]
511             val arrayField = (getterCall.target as PropertyGet).target
512             // whether this field is static or not
513             val isStatic = arrayField !is PropertyGet
514             val arrName = if (isStatic) arrayField else (arrayField as PropertyGet).property
515             val getter = ElementGet(arrName, index)
516             return if (isStatic) { //intArr[index]
517                 getter
518             } else { //A.intArr[0]
519                 val call = PropertyGet()
520                 call.target = (arrayField as PropertyGet).target
521                 val name = Name()
522                 name.identifier = getter.toSource()
523                 call.property = name
524                 call
525             }
526         }
527     }
528 
529 
530     // receiver data flow
531     inner class ReceiverResolver(private val receiverName: String) : NodeVisitor {
532         var receiver: AstNode? = null
533         override fun visit(node: AstNode): Boolean {
534             if (node is VariableInitializer) {
535                 if (node.target.toSource() == receiverName) {
536                     receiver = node.initializer
537                     return false
538                 }
539             }
540             return true
541         }
542     }
543 
544     inner class AtomicOperationsInliner : NodeVisitor {
545         override fun visit(node: AstNode?): Boolean {
546             // inline atomic operations
547             if (node is FunctionCall) {
548                 if (node.target is PropertyGet) {
549                     val funcName = (node.target as PropertyGet).property
550                     var field = (node.target as PropertyGet).target
551                     if (field.toSource().matches(Regex(RECEIVER))) {
552                         val receiverName = field.toSource() // $receiver_i
553                         val rr = ReceiverResolver(receiverName)
554                         node.enclosingFunction.visit(rr)
555                         if (rr.receiver != null) {
556                             field = rr.receiver
557                         }
558                     }
559                     field.eraseAtomicFieldFromUncheckedCast()?.let { field = it }
560                     val args = node.arguments
561                     val inlined = node.inlineAtomicOperation(funcName.toSource(), field, args)
562                     return !inlined
563                 }
564             }
565             return true
566         }
567     }
568 
569     private fun AstNode.eraseAtomicFieldFromUncheckedCast(): AstNode? {
570         if (this is ParenthesizedExpression && expression is ConditionalExpression) {
571             val testExpression = (expression as ConditionalExpression).testExpression
572             if (testExpression is FunctionCall && testExpression.target.toSource() == KOTLIN_TYPE_CHECK) {
573                 // type check
574                 val typeToCast = testExpression.arguments[1]
575                 if ((typeToCast as Name).identifier == ATOMIC_REF) {
576                     // unchecked type cast -> erase atomic field itself
577                     return (testExpression.arguments[0] as Assignment).right
578                 }
579             }
580         }
581         return null
582     }
583 
584     private fun AstNode.isThisNode(): Boolean {
585         return when(this) {
586             is PropertyGet -> {
587                 target.isThisNode()
588             }
589             is FunctionCall -> {
590                 target.isThisNode()
591             }
592             else -> {
593                 (this.type == Token.THIS)
594             }
595         }
596     }
597 
598     private fun PropertyGet.resolvePropName(): String {
599         val target = this.target
600         return if (target is PropertyGet) {
601             "${target.resolvePropName()}.${property.toSource()}"
602         } else {
603             property.toSource()
604         }
605     }
606 
607     private fun AstNode.scopedSource(): String {
608         if (this.isThisNode()) {
609             if (this is PropertyGet) {
610                 val property = resolvePropName()
611                 return "$SCOPE.$property"
612             } else if (this is FunctionCall && this.target is PropertyGet) {
613                 // check that this function call is getting array element
614                 if (this.target is PropertyGet) {
615                     val funcName = (this.target as PropertyGet).property.toSource()
616                     if (Regex(GET_ELEMENT).matches(funcName)) {
617                         val property = (this.target as PropertyGet).resolvePropName()
618                         return "$SCOPE.$property(${this.arguments[0].toSource()})"
619                     }
620                 }
621             } else if (this.type == Token.THIS) {
622                 return SCOPE
623             }
624         }
625         return this.toSource()
626     }
627 
628     private fun FunctionCall.inlineAtomicOperation(
629         funcName: String,
630         field: AstNode,
631         args: List<AstNode>
632     ): Boolean {
633         val f = field.scopedSource()
634         val code = when (funcName) {
635             "atomicfu\$getAndSet" -> {
636                 val arg = args[0].toSource()
637                 "(function($SCOPE) {var oldValue = $f; $f = $arg; return oldValue;})()"
638             }
639             "atomicfu\$compareAndSet" -> {
640                 val expected = args[0].scopedSource()
641                 val updated = args[1].scopedSource()
642                 val equals = if (expected == "null") "==" else "==="
643                 "(function($SCOPE) {return $f $equals $expected ? function() { $f = $updated; return true }() : false})()"
644             }
645             "atomicfu\$getAndIncrement" -> {
646                 "(function($SCOPE) {return $f++;})()"
647             }
648 
649             "atomicfu\$getAndIncrement\$long" -> {
650                 "(function($SCOPE) {var oldValue = $f; $f = $f.inc(); return oldValue;})()"
651             }
652 
653             "atomicfu\$getAndDecrement" -> {
654                 "(function($SCOPE) {return $f--;})()"
655             }
656 
657             "atomicfu\$getAndDecrement\$long" -> {
658                 "(function($SCOPE) {var oldValue = $f; $f = $f.dec(); return oldValue;})()"
659             }
660 
661             "atomicfu\$getAndAdd" -> {
662                 val arg = args[0].scopedSource()
663                 "(function($SCOPE) {var oldValue = $f; $f += $arg; return oldValue;})()"
664             }
665 
666             "atomicfu\$getAndAdd\$long" -> {
667                 val arg = args[0].scopedSource()
668                 "(function($SCOPE) {var oldValue = $f; $f = $f.add($arg); return oldValue;})()"
669             }
670 
671             "atomicfu\$addAndGet" -> {
672                 val arg = args[0].scopedSource()
673                 "(function($SCOPE) {$f += $arg; return $f;})()"
674             }
675 
676             "atomicfu\$addAndGet\$long" -> {
677                 val arg = args[0].scopedSource()
678                 "(function($SCOPE) {$f = $f.add($arg); return $f;})()"
679             }
680 
681             "atomicfu\$incrementAndGet" -> {
682                 "(function($SCOPE) {return ++$f;})()"
683             }
684 
685             "atomicfu\$incrementAndGet\$long" -> {
686                 "(function($SCOPE) {return $f = $f.inc();})()"
687             }
688 
689             "atomicfu\$decrementAndGet" -> {
690                 "(function($SCOPE) {return --$f;})()"
691             }
692 
693             "atomicfu\$decrementAndGet\$long" -> {
694                 "(function($SCOPE) {return $f = $f.dec();})()"
695             }
696             else -> null
697         }
698         if (code != null) {
699             this.setImpl(code)
700             return true
701         }
702         return false
703     }
704 
705     private fun FunctionCall.setImpl(code: String) {
706         val p = Parser(CompilerEnvirons())
707         val node = p.parse(code, null, 0)
708         if (node.firstChild != null) {
709             val expr = (node.firstChild as ExpressionStatement).expression
710             this.target = (expr as FunctionCall).target
711             val thisNode = Parser(CompilerEnvirons()).parse("this", null, 0)
712             this.arguments = listOf((thisNode.firstChild as ExpressionStatement).expression)
713         }
714     }
715 }
716 
717 private class EmptyLine : EmptyExpression() {
toSourcenull718     override fun toSource(depth: Int) = "\n"
719 }
720 
721 fun main(args: Array<String>) {
722     if (args.size !in 1..2) {
723         println("Usage: AtomicFUTransformerKt <dir> [<output>]")
724         return
725     }
726     val t = AtomicFUTransformerJS(File(args[0]), File(args[1]))
727     t.transform()
728 }