• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 fieldDelegates = mutableMapOf<String, String>()
56     private val delegatedProperties = mutableMapOf<String, String>()
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(TransformVisitor())
85         root.visit(AtomicOperationsInliner())
86         return root.eraseGetValue().toByteArray()
87     }
88 
89     // erase getting value of atomic field
90     private fun AstNode.eraseGetValue(): String {
91         var res = this.toSource()
92         val primitiveGetValue = MANGLE_VALUE_REGEX
93         val arrayGetElement = ARRAY_GET_ELEMENT_REGEX
94         while (res.contains(arrayGetElement)) {
95             res = res.replace(arrayGetElement) { matchResult ->
96                 val greedyToLastClosingParen = matchResult.groupValues[1]
97                 var balance = 1
98                 var indexEndPos = 0
99                 for (i in 0 until greedyToLastClosingParen.length) {
100                     val c = greedyToLastClosingParen[i]
101                     if (c == '(') balance++
102                     if (c == ')') balance--
103                     if (balance == 0) {
104                         indexEndPos = i
105                         break
106                     }
107                 }
108                 val closingParen = indexEndPos == greedyToLastClosingParen.lastIndex
109                 if (balance == 1) {
110                     "[$greedyToLastClosingParen]"
111                 } else {
112                     "[${greedyToLastClosingParen.substring(0, indexEndPos)}]${greedyToLastClosingParen.substring(indexEndPos + 1)}${if (!closingParen) ")" else ""}"
113                 }
114             }
115         }
116         return res.replace(primitiveGetValue) { "" }
117     }
118 
119     inner class DependencyEraser : NodeVisitor {
120         private fun isAtomicfuDependency(node: AstNode) =
121             (node.type == Token.STRING && node.toSource() == KOTLINX_ATOMICFU)
122 
123         private fun isAtomicfuModule(node: AstNode) =
124             (node.type == Token.NAME && node.toSource().matches(Regex(MODULE_KOTLINX_ATOMICFU)))
125 
126         override fun visit(node: AstNode): Boolean {
127             when (node.type) {
128                 Token.ARRAYLIT -> {
129                     // erasing 'kotlinx-atomicfu' from the list of defined dependencies
130                     val elements = (node as ArrayLiteral).elements as MutableList
131                     val it = elements.listIterator()
132                     while (it.hasNext()) {
133                         val arg = it.next()
134                         if (isAtomicfuDependency(arg)) {
135                             it.remove()
136                         }
137                     }
138                 }
139                 Token.FUNCTION -> {
140                     if (node is FunctionNode) {
141                         val it = node.params.listIterator()
142                         while (it.hasNext()) {
143                             // erasing 'kotlinx-atomicfu' module passed as parameter
144                             if (isAtomicfuModule(it.next())) {
145                                 it.remove()
146                             }
147                         }
148                     }
149                 }
150                 Token.CALL -> {
151                     if (node is FunctionCall && node.target.toSource() == FACTORY) {
152                         val it = node.arguments.listIterator()
153                         while (it.hasNext()) {
154                             val arg = it.next()
155                             when (arg.type) {
156                                 Token.GETELEM -> {
157                                     // erasing 'kotlinx-atomicfu' dependency as factory argument
158                                     if (isAtomicfuDependency((arg as ElementGet).element)) {
159                                         it.remove()
160                                     }
161                                 }
162                                 Token.CALL -> {
163                                     // erasing require of 'kotlinx-atomicfu' dependency
164                                     if ((arg as FunctionCall).target.toSource() == REQUIRE) {
165                                         if (isAtomicfuDependency(arg.arguments[0])) {
166                                             it.remove()
167                                         }
168                                     }
169                                 }
170                             }
171                         }
172                     }
173                 }
174                 Token.GETELEM -> {
175                     if (isAtomicfuDependency((node as ElementGet).element)) {
176                         val enclosingNode = node.parent
177                         // erasing the check whether 'kotlinx-atomicfu' is defined
178                         if (enclosingNode.type == Token.TYPEOF) {
179                             if (enclosingNode.parent.parent.type == Token.IF) {
180                                 val ifStatement = enclosingNode.parent.parent as IfStatement
181                                 val falseKeyword = KeywordLiteral()
182                                 falseKeyword.type = Token.FALSE
183                                 ifStatement.condition = falseKeyword
184                                 val oneLineBlock = Block()
185                                 oneLineBlock.addStatement(EmptyLine())
186                                 ifStatement.thenPart = oneLineBlock
187                             }
188                         }
189 
190                     }
191                 }
192                 Token.BLOCK -> {
193                     // erasing importsForInline for 'kotlinx-atomicfu'
194                     for (stmt in node) {
195                         if (stmt is ExpressionStatement) {
196                             val expr = stmt.expression
197                             if (expr is Assignment && expr.left is ElementGet) {
198                                 if (isAtomicfuDependency((expr.left as ElementGet).element)) {
199                                     node.replaceChild(stmt, EmptyLine())
200                                 }
201                             }
202                         }
203                     }
204                 }
205             }
206             return true
207         }
208     }
209 
210     inner class AtomicConstructorDetector : NodeVisitor {
211         private fun kotlinxAtomicfuModuleName(name: String) = "$MODULE_KOTLINX_ATOMICFU.$KOTLINX_ATOMICFU_PACKAGE.$name"
212 
213         override fun visit(node: AstNode?): Boolean {
214             if (node is Block) {
215                 for (stmt in node) {
216                     if (stmt is VariableDeclaration) {
217                         val varInit = stmt.variables[0] as VariableInitializer
218                         if (varInit.initializer is PropertyGet) {
219                             val initializer = varInit.initializer.toSource()
220                             if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($ATOMIC_CONSTRUCTOR|$ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY)""")))) {
221                                 atomicConstructors.add(varInit.target.toSource())
222                                 node.replaceChild(stmt, EmptyLine())
223                             } else if (initializer.matches(Regex(kotlinxAtomicfuModuleName(TRACE_CONSTRUCTOR)))) {
224                                 traceConstructors.add(varInit.target.toSource())
225                                 node.replaceChild(stmt, EmptyLine())
226                             } else if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($LOCKS|$TRACE_FORMAT_CONSTRUCTOR|$TRACE_BASE_CLASS|$TRACE_NAMED)""")))) {
227                                 node.replaceChild(stmt, EmptyLine())
228                             }
229                         }
230                     }
231                 }
232             }
233             if (node is PropertyGet && node.property.toSource().matches(Regex(TRACE_FORMAT_FORMAT))) {
234                 val target = node.target
235                 node.property = Name().also { it.identifier = "emptyProperty" }
236                 if (target is PropertyGet && target.property.toSource().matches(Regex(PROTOTYPE))) {
237                     traceFormatObjects.add(target.target.toSource())
238                 }
239             }
240             if (node is VariableInitializer && node.initializer is PropertyGet) {
241                 val initializer = node.initializer.toSource()
242                 if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) {
243                     node.initializer = null
244                 }
245                 if (initializer.matches(Regex(kotlinxAtomicfuModuleName("""($ATOMIC_CONSTRUCTOR|$ATOMIC_CONSTRUCTOR_BINARY_COMPATIBILITY)""")))) {
246                     atomicConstructors.add(node.target.toSource())
247                     node.initializer = null
248                 }
249                 if (initializer.matches(Regex(kotlinxAtomicfuModuleName(ATOMIC_ARRAY_CONSTRUCTOR)))) {
250                     val initialValue = when (initializer.substringAfterLast('$')) {
251                         "int" -> "0"
252                         "long" -> "0"
253                         "boolean" -> "false"
254                         else -> null
255                     }
256                     atomicArrayConstructors[node.target.toSource()] = initialValue
257                     node.initializer = null
258                 }
259                 return false
260             } else if (node is Assignment && node.right is PropertyGet) {
261                 val initializer = node.right.toSource()
262                 if (initializer.matches(Regex(REENTRANT_LOCK_ATOMICFU_SINGLETON))) {
263                     node.right = Name().also { it.identifier = "null" }
264                     return false
265                 }
266             }
267             return true
268         }
269     }
270 
271     inner class FieldDelegatesVisitor : NodeVisitor {
272         override fun visit(node: AstNode?): Boolean {
273             if (node is FunctionCall) {
274                 val functionName = node.target.toSource()
275                 if (atomicConstructors.contains(functionName)) {
276                     if (node.parent is Assignment) {
277                         val assignment = node.parent as Assignment
278                         val atomicField = assignment.left
279                         val constructorBlock = ((node.parent.parent as? ExpressionStatement)?.parent as? Block)
280                                 ?: abort("Incorrect tree structure of the constructor block initializing ${node.parent.toSource()}")
281                         // check if there is a delegate field initialized by the reference to this atomic
282                         for (stmt in constructorBlock) {
283                             if (stmt is ExpressionStatement) {
284                                 if (stmt.expression is Assignment) {
285                                     val delegateAssignment = stmt.expression as Assignment
286                                     if (delegateAssignment.right is PropertyGet) {
287                                         val initializer = delegateAssignment.right as PropertyGet
288                                         if (initializer.toSource() == atomicField.toSource()) {
289                                             // register field delegate and the original atomic field
290                                             fieldDelegates[(delegateAssignment.left as PropertyGet).property.toSource()] =
291                                                     (atomicField as PropertyGet).property.toSource()
292                                         }
293                                     }
294                                 }
295                             }
296                         }
297                     }
298                 }
299             }
300             return true
301         }
302     }
303 
304     inner class DelegatedPropertyAccessorsVisitor : NodeVisitor {
305         override fun visit(node: AstNode?): Boolean {
306             if (node is PropertyGet) {
307                 if (node.target is PropertyGet) {
308                     if ((node.target as PropertyGet).property.toSource() in fieldDelegates && node.property.toSource() == MANGLED_VALUE_PROP) {
309                         if (node.parent is ReturnStatement) {
310                             val getter = ((((node.parent.parent as? Block)?.parent as? FunctionNode)?.parent as? ObjectProperty)?.parent as? ObjectLiteral)
311                                     ?: abort("Incorrect tree structure of the accessor for the property delegated " +
312                                             "to the atomic field ${fieldDelegates[node.target.toSource()]}")
313                             val definePropertyCall = getter.parent as FunctionCall
314                             val stringLiteral = definePropertyCall.arguments[1] as? StringLiteral
315                                     ?: abort ("Object.defineProperty invocation should take a property name as the second argument")
316                             val delegatedProperty = stringLiteral.value.toString()
317                             delegatedProperties[delegatedProperty] = (node.target as PropertyGet).property.toSource()
318                         }
319                     }
320                 }
321 
322             }
323             return true
324         }
325     }
326 
327     inner class TransformVisitor : NodeVisitor {
328         override fun visit(node: AstNode): Boolean {
329             // remove atomic constructors from classes fields
330             if (node is FunctionCall) {
331                 val functionName = node.target.toSource()
332                 if (atomicConstructors.contains(functionName)) {
333                     if (node.parent is Assignment) {
334                         val valueNode = node.arguments[0]
335                         (node.parent as Assignment).right = valueNode
336                     }
337                     return true
338                 } else if (atomicArrayConstructors.contains(functionName)) {
339                     val arrayConstructor = Name()
340                     arrayConstructor.identifier = ARRAY
341                     node.target = arrayConstructor
342                     atomicArrayConstructors[functionName]?.let {
343                         val arrayConsCall = FunctionCall()
344                         arrayConsCall.target = node.target
345                         arrayConsCall.arguments = node.arguments
346                         val target = PropertyGet()
347                         val fill = Name()
348                         fill.identifier = FILL
349                         target.target = arrayConsCall
350                         target.property = fill
351                         node.target = target
352                         val initialValue = Name()
353                         initialValue.identifier = it
354                         node.arguments = listOf(initialValue)
355                     }
356                     return true
357                 } else if (node.target is PropertyGet) {
358                     if ((node.target as PropertyGet).target is FunctionCall) {
359                         val atomicOperationTarget = node.target as PropertyGet
360                         val funcCall = atomicOperationTarget.target as FunctionCall
361                         if (funcCall.target is PropertyGet) {
362                             val getterCall = (funcCall.target as PropertyGet).property
363                             if (Regex(GET_ELEMENT).matches(getterCall.toSource())) {
364                                 val getter = getArrayElement(funcCall)
365                                 atomicOperationTarget.target = getter
366                             }
367                         }
368                     }
369                 }
370             }
371             // remove value property call
372             if (node is PropertyGet) {
373                 if (node.property.toSource() == MANGLED_VALUE_PROP) {
374                     // check whether atomic operation is performed on the type casted atomic field
375                     node.target.eraseAtomicFieldFromUncheckedCast()?.let { node.target = it }
376                     // A.a.value
377                     if (node.target.type == Token.GETPROP) {
378                         val clearField = node.target as PropertyGet
379                         val targetNode = clearField.target
380                         val clearProperety = clearField.property
381                         node.setLeftAndRight(targetNode, clearProperety)
382                     }
383                     // other cases with $receiver.kotlinx$atomicfu$value in inline functions
384                     else if (node.target.toSource().matches(Regex(RECEIVER))) {
385                         val receiverName = node.target.toSource() // $receiver_i
386                         val rr = ReceiverResolver(receiverName)
387                         node.enclosingFunction.visit(rr)
388                         rr.receiver?.let { node.target = it }
389                     }
390                 }
391                 if (node.property.toSource() in delegatedProperties) {
392                     // replace delegated property name with the name of the original atomic field
393                     val fieldDelegate = delegatedProperties[node.property.toSource()]
394                     val originalField = fieldDelegates[fieldDelegate]!!
395                     node.property = Name().apply { identifier = originalField }
396                 }
397                 // replace Atomic*Array.size call with `length` property on the pure type js array
398                 if (node.property.toSource() == ARRAY_SIZE) {
399                     node.property = Name().also { it.identifier = LENGTH }
400                 }
401             }
402             if (node is Block) {
403                 for (stmt in node) {
404                     if (stmt is ExpressionStatement) {
405                         if (stmt.expression is Assignment) {
406                             // erase field initialisation
407                             val assignment = stmt.expression as Assignment
408                             if (assignment.right is FunctionCall) {
409                                 val functionName = (assignment.right as FunctionCall).target.toSource()
410                                 if (traceConstructors.contains(functionName)) {
411                                     node.replaceChild(stmt, EmptyLine())
412                                 }
413                             }
414                         }
415                         if (stmt.expression is FunctionCall) {
416                             // erase append(text) call
417                             val funcNode = (stmt.expression as FunctionCall).target
418                             if (funcNode is PropertyGet && funcNode.property.toSource().matches(Regex(TRACE_APPEND))) {
419                                 node.replaceChild(stmt, EmptyLine())
420                             }
421                         }
422                     }
423                 }
424             }
425             if (node is Assignment && node.left is PropertyGet) {
426                 val left = node.left as PropertyGet
427                 if (traceFormatObjects.contains(left.target.toSource())) {
428                     if (node.right is FunctionCall) {
429                         // TraceFormatObject initialization
430                         (node.right as FunctionCall).arguments = listOf(Name().also { it.identifier = "null" })
431                     }
432                 }
433             }
434             // remove TraceFormatObject constructor definition
435             if (node is FunctionNode && traceFormatObjects.contains(node.name)) {
436                 val body  = node.body
437                 for (stmt in body) { body.replaceChild(stmt, EmptyLine()) }
438             }
439             // remove TraceFormat from TraceFormatObject interfaces
440             if (node is Assignment && node.left is PropertyGet && node.right is ObjectLiteral) {
441                 val left = node.left as PropertyGet
442                 val metadata = node.right as ObjectLiteral
443                 if (traceFormatObjects.contains(left.target.toSource())) {
444                     for (e in metadata.elements) {
445                         if (e.right is ArrayLiteral) {
446                             val array = (e.right as ArrayLiteral).toSource()
447                             if (array.contains(TRACE_FORMAT)) {
448                                 (e.right as ArrayLiteral).elements = emptyList()
449                             }
450                         }
451                     }
452                 }
453             }
454             return true
455         }
456 
457         private fun getArrayElement(getterCall: FunctionCall): AstNode {
458             val index = getterCall.arguments[0]
459             val arrayField = (getterCall.target as PropertyGet).target
460             // whether this field is static or not
461             val isStatic = arrayField !is PropertyGet
462             val arrName = if (isStatic) arrayField else (arrayField as PropertyGet).property
463             val getter = ElementGet(arrName, index)
464             return if (isStatic) { //intArr[index]
465                 getter
466             } else { //A.intArr[0]
467                 val call = PropertyGet()
468                 call.target = (arrayField as PropertyGet).target
469                 val name = Name()
470                 name.identifier = getter.toSource()
471                 call.property = name
472                 call
473             }
474         }
475     }
476 
477 
478     // receiver data flow
479     inner class ReceiverResolver(private val receiverName: String) : NodeVisitor {
480         var receiver: AstNode? = null
481         override fun visit(node: AstNode): Boolean {
482             if (node is VariableInitializer) {
483                 if (node.target.toSource() == receiverName) {
484                     receiver = node.initializer
485                     return false
486                 }
487             }
488             return true
489         }
490     }
491 
492     inner class AtomicOperationsInliner : NodeVisitor {
493         override fun visit(node: AstNode?): Boolean {
494             // inline atomic operations
495             if (node is FunctionCall) {
496                 if (node.target is PropertyGet) {
497                     val funcName = (node.target as PropertyGet).property
498                     var field = (node.target as PropertyGet).target
499                     if (field.toSource().matches(Regex(RECEIVER))) {
500                         val receiverName = field.toSource() // $receiver_i
501                         val rr = ReceiverResolver(receiverName)
502                         node.enclosingFunction.visit(rr)
503                         if (rr.receiver != null) {
504                             field = rr.receiver
505                         }
506                     }
507                     field.eraseAtomicFieldFromUncheckedCast()?.let { field = it }
508                     val args = node.arguments
509                     val inlined = node.inlineAtomicOperation(funcName.toSource(), field, args)
510                     return !inlined
511                 }
512             }
513             return true
514         }
515     }
516 
517     private fun AstNode.eraseAtomicFieldFromUncheckedCast(): AstNode? {
518         if (this is ParenthesizedExpression && expression is ConditionalExpression) {
519             val testExpression = (expression as ConditionalExpression).testExpression
520             if (testExpression is FunctionCall && testExpression.target.toSource() == KOTLIN_TYPE_CHECK) {
521                 // type check
522                 val typeToCast = testExpression.arguments[1]
523                 if ((typeToCast as Name).identifier == ATOMIC_REF) {
524                     // unchecked type cast -> erase atomic field itself
525                     return (testExpression.arguments[0] as Assignment).right
526                 }
527             }
528         }
529         return null
530     }
531 
532     private fun AstNode.isThisNode(): Boolean {
533         return when(this) {
534             is PropertyGet -> {
535                 target.isThisNode()
536             }
537             is FunctionCall -> {
538                 target.isThisNode()
539             }
540             else -> {
541                 (this.type == Token.THIS)
542             }
543         }
544     }
545 
546     private fun PropertyGet.resolvePropName(): String {
547         val target = this.target
548         return if (target is PropertyGet) {
549             "${target.resolvePropName()}.${property.toSource()}"
550         } else {
551             property.toSource()
552         }
553     }
554 
555     private fun AstNode.scopedSource(): String {
556         if (this.isThisNode()) {
557             if (this is PropertyGet) {
558                 val property = resolvePropName()
559                 return "$SCOPE.$property"
560             } else if (this is FunctionCall && this.target is PropertyGet) {
561                 // check that this function call is getting array element
562                 if (this.target is PropertyGet) {
563                     val funcName = (this.target as PropertyGet).property.toSource()
564                     if (Regex(GET_ELEMENT).matches(funcName)) {
565                         val property = (this.target as PropertyGet).resolvePropName()
566                         return "$SCOPE.$property(${this.arguments[0].toSource()})"
567                     }
568                 }
569             } else if (this.type == Token.THIS) {
570                 return SCOPE
571             }
572         }
573         return this.toSource()
574     }
575 
576     private fun FunctionCall.inlineAtomicOperation(
577         funcName: String,
578         field: AstNode,
579         args: List<AstNode>
580     ): Boolean {
581         val f = field.scopedSource()
582         val code = when (funcName) {
583             "atomicfu\$getAndSet" -> {
584                 val arg = args[0].toSource()
585                 "(function($SCOPE) {var oldValue = $f; $f = $arg; return oldValue;})()"
586             }
587             "atomicfu\$compareAndSet" -> {
588                 val expected = args[0].scopedSource()
589                 val updated = args[1].scopedSource()
590                 val equals = if (expected == "null") "==" else "==="
591                 "(function($SCOPE) {return $f $equals $expected ? function() { $f = $updated; return true }() : false})()"
592             }
593             "atomicfu\$getAndIncrement" -> {
594                 "(function($SCOPE) {return $f++;})()"
595             }
596 
597             "atomicfu\$getAndIncrement\$long" -> {
598                 "(function($SCOPE) {var oldValue = $f; $f = $f.inc(); return oldValue;})()"
599             }
600 
601             "atomicfu\$getAndDecrement" -> {
602                 "(function($SCOPE) {return $f--;})()"
603             }
604 
605             "atomicfu\$getAndDecrement\$long" -> {
606                 "(function($SCOPE) {var oldValue = $f; $f = $f.dec(); return oldValue;})()"
607             }
608 
609             "atomicfu\$getAndAdd" -> {
610                 val arg = args[0].scopedSource()
611                 "(function($SCOPE) {var oldValue = $f; $f += $arg; return oldValue;})()"
612             }
613 
614             "atomicfu\$getAndAdd\$long" -> {
615                 val arg = args[0].scopedSource()
616                 "(function($SCOPE) {var oldValue = $f; $f = $f.add($arg); return oldValue;})()"
617             }
618 
619             "atomicfu\$addAndGet" -> {
620                 val arg = args[0].scopedSource()
621                 "(function($SCOPE) {$f += $arg; return $f;})()"
622             }
623 
624             "atomicfu\$addAndGet\$long" -> {
625                 val arg = args[0].scopedSource()
626                 "(function($SCOPE) {$f = $f.add($arg); return $f;})()"
627             }
628 
629             "atomicfu\$incrementAndGet" -> {
630                 "(function($SCOPE) {return ++$f;})()"
631             }
632 
633             "atomicfu\$incrementAndGet\$long" -> {
634                 "(function($SCOPE) {return $f = $f.inc();})()"
635             }
636 
637             "atomicfu\$decrementAndGet" -> {
638                 "(function($SCOPE) {return --$f;})()"
639             }
640 
641             "atomicfu\$decrementAndGet\$long" -> {
642                 "(function($SCOPE) {return $f = $f.dec();})()"
643             }
644             else -> null
645         }
646         if (code != null) {
647             this.setImpl(code)
648             return true
649         }
650         return false
651     }
652 
653     private fun FunctionCall.setImpl(code: String) {
654         val p = Parser(CompilerEnvirons())
655         val node = p.parse(code, null, 0)
656         if (node.firstChild != null) {
657             val expr = (node.firstChild as ExpressionStatement).expression
658             this.target = (expr as FunctionCall).target
659             val thisNode = Parser(CompilerEnvirons()).parse("this", null, 0)
660             this.arguments = listOf((thisNode.firstChild as ExpressionStatement).expression)
661         }
662     }
663 }
664 
665 private class EmptyLine : EmptyExpression() {
toSourcenull666     override fun toSource(depth: Int) = "\n"
667 }
668 
669 fun main(args: Array<String>) {
670     if (args.size !in 1..2) {
671         println("Usage: AtomicFUTransformerKt <dir> [<output>]")
672         return
673     }
674     val t = AtomicFUTransformerJS(File(args[0]), File(args[1]))
675     t.transform()
676 }