Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Meta-Programming with Kotlin - BlrKotlin Meetup

Ce1ca64f3265f01a8718a622427f0a1d?s=47 Jitin
January 25, 2020

Meta-Programming with Kotlin - BlrKotlin Meetup

Kotlin has become go-to language for Android developers all over the world and the language itself has a large number of feature sets.
With greater acceptance of language, we look at how we can leverage power of Kotlin for meta-programming.
Through the talk, we'll introduce what Meta-Programming is and how we do it on day to day basis. We'll see how using Kotlin's internal compiler API, we can parse kotlin files, pull data out of it and go through various applications of this data such as custom documentation for API code and much more.
We'll also look at various approaches of source code generation for Kotlin files and how we can leverage them in projects.

Ce1ca64f3265f01a8718a622427f0a1d?s=128

Jitin

January 25, 2020
Tweet

More Decks by Jitin

Other Decks in Technology

Transcript

  1. Meta-Programming with Kotlin @_jitinsharma Jitin Sharma

  2. Open Ended

  3. Open Ended Automation

  4. Open Ended Automation Kotlin

  5. Meta-Programming

  6. Code Generators Code Parsers

  7. Code Generators Code Parsers Annotation Processors?

  8. Code Generators Code Parsers Annotation Processors? Retrofit Gson Dagger

  9. Source Code Parsers

  10. Source Code Parsers? • Gain insights over large codebase •

    Build static analysis tools • Documentation!
  11. Anatomy of Source Code

  12. File Class class… Functions Variables • abstract • interface •

    static • …. • private • protected • public • ….
  13. File Classes Functions Variables class… Functions Variables • abstract •

    interface • object • companion • data • …. • private • internal • typealias • ….
  14. val source: String = System.getProperty("user.dir") class Generator : GeneratorConfig {

    private var replaceIfNecessary = false override fun createConfig(config: Config) { } inner class Template { ... } data class Model(...) }
  15. Program Structure Interface

  16. PSI • Provides hierarchal content of code • Works across

    languages
  17. PSI PsiFileFactory .getInstance(project) .createFileFromText(KotlinLanguage.INSTANCE, text)

  18. PSI PsiFileFactory .getInstance(project) .createFileFromText(KotlinLanguage.INSTANCE, text) private val project by lazy

    { KotlinCoreEnvironment.createForProduction( Disposer.newDisposable(), CompilerConfiguration(), EnvironmentConfigFiles.JVM_CONFIG_FILES ).project }
  19. implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.3.61")

  20. PsiFile PsiElement PsiElement PsiElement …

  21. PsiFile PsiElement PsiElement PsiElement … KtFile

  22. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  23. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  24. ArrowView.kt KtFile }

  25. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  26. import android.content.Context import android.graphics.Canvas import android.graphics.Color KtImportList KtImportDirective }

  27. import android.content.Context import android.graphics.Canvas import android.graphics.Color KtImportList KtImportAlias as AndroidColor

    KtImportDirective }
  28. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  29. class ArrowView(context: Context, sides: Float, val base: Float) : View(context)

    { KtClass KtSuperTypeEntries } } KtPrimaryConstructor }
  30. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  31. private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context)

    imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView } KtParameter KtModifierKeywordToken KtTypeReference KtBlockExpression } } } } KtExpression
  32. private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context)

    imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView } /** * Generates an imageview dynamically */ KDoc }
  33. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  34. PSI-Tree ktFile.acceptChildren(// PsiElementVisitor()) ktFile.acceptChildren(classRecursiveVisitor { ktClass -> }) ktFile.acceptChildren(namedFunctionRecursiveVisitor {

    ktFunction -> })
  35. None
  36. Plugin

  37. Source Code as Data Source Code

  38. Source Code as Data Source Code @Serializable data class ParsedFile(

    var name: String, var doc: String? = null, @SerialName("classes") var parsedClasses: List<ParsedClass>? = null, @SerialName("functions") var parsedFunctions: List<ParsedFunction>? = null, @SerialName("properties") var parsedVariables: List<ParsedVariable>? = null )
  39. Source Code as Data Source Code @Serializable data class ParsedFile(

    var name: String, var doc: String? = null, @SerialName("classes") var parsedClasses: List<ParsedClass>? = null, @SerialName("functions") var parsedFunctions: List<ParsedFunction>? = null, @SerialName("properties") var parsedVariables: List<ParsedVariable>? = null )
  40. Documentation • dokka != configurable • Build custom documentation

  41. Static Analysis • Access to every piece of code in

    your codebase • ktlint!
  42. Source Code Generators

  43. Source Code Generators? • Avoid manual boilerplate code • Promote

    templating • Create more formalised code base
  44. Writing Source Code • Easy string manipulation with regex and

    indent extensions • Java8 provides better file operations and Streams! • Coroutines for async
  45. Writing Templates val text = """ class MainActivity : AppCompatActivity()

    { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } """.trimIndent() Files.write(path, text)
  46. Writing Templates val text = """ class $activityName : BaseActivity()

    { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.$layoutName) } } """.trimIndent() Files.write(path, text)
  47. Structured Codegen https://github.com/square/kotlinpoet

  48. Structured Codegen FileSpec.builder("in.jitinsharma", "MainActivity.kt") TypeSpec.classBuilder() FunSpec.builder("onCreate") addParameter() addModifiers(KModifier.INTERNAL) superclass(TypeVariableName.invoke("AppCompatActivity"))

  49. Schema based Codegen { "source": "...", “type": "...", "paths": []

    } Source Code
  50. Schema based Codegen { "sourceClass": "MainActivity.kt", "sourceXml": "activity_main", "adapter": "CountryListAdapter",

    "views": [ { "layoutFileName": "country_list_header.xml", "layoutType": "TextView", "viewHolderName": "CountryListHeaderViewHolder" } ] }
  51. SqlDelight CREATE TABLE Photos ( id INTEGER NOT NULL, title

    TEXT NOT NULL ); insert: INSERT INTO Photos (id, title) VALUES (?, ?); getAll: SELECT * FROM Photos;
  52. SqlDelight interface PhotoQueries : Transacter { fun <T : Any>

    getAll(mapper: ( id: String, title: String ) -> T): Query<T> fun getAll(): Query<Photos> fun insert( id: String, title: String ) }
  53. val db = Database(AndroidSqliteDriver(Database.Schema, context, “photo.db”)) db.photoQueries.getAll() -> List<Photos> SqlDelight

    - MPP
  54. None
  55. <T : Closeable?, R> T.use()

  56. <T : Closeable?, R> T.use() codeString .lineSequence() .map { line

    -> }
  57. <T : Closeable?, R> T.use() codeString .lineSequence() .map { line

    -> } Files.find(path, 10, BiPredicate { path, _ -> })
  58. <T : Closeable?, R> T.use() codeString .lineSequence() .map { line

    -> } Files.find(path, 10, BiPredicate { path, _ -> }) Files.write( path, classString.toByteArray(), StandardOpenOption.CREATE_NEW, StandardOpenOption.TRUNCATE_EXISTING)
  59. . ├── app ├── module-1 ├── module-2 ├── … └──

    tools-module java -jar project/build/libs/tools.jar --param
  60. . ├── app ├── module-1 ├── module-2 ├── … └──

    script.kts kscript
  61. Fun fact

  62. Thanks @_jitinsharma