├── .github └── workflows │ ├── publish-release.yml │ └── publish-snapshot.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── build-logic ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── Version.kt │ ├── kmp-library-convention.gradle.kts │ ├── print-sdk-version-convention.gradle.kts │ └── publication-convention.gradle.kts ├── build.gradle.kts ├── core ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── kotl │ └── core │ ├── annotation │ └── TLDSL.kt │ ├── builder │ ├── BuildTLCallable.kt │ ├── BuildTLConstructor.kt │ ├── BuildTLConstructorDescriptor.kt │ ├── BuildTLFunction.kt │ ├── BuildTLTypeDescriptor.kt │ ├── BuildTLVector.kt │ └── BuildTLVectorDescriptor.kt │ ├── decoder │ ├── ByteArrayInput.kt │ └── FromByteArray.kt │ ├── descriptor │ ├── TLBooleanDescriptor.kt │ ├── TLBytesDescriptor.kt │ ├── TLConstructorDescriptor.kt │ ├── TLDoubleDescriptor.kt │ ├── TLExpressionDescriptor.kt │ ├── TLInt128Descriptor.kt │ ├── TLInt32Descriptor.kt │ ├── TLInt64Descriptor.kt │ ├── TLIntDescriptor.kt │ ├── TLNullDescriptor.kt │ ├── TLPrimitiveDescriptor.kt │ ├── TLStringDescriptor.kt │ ├── TLTypeDescriptor.kt │ └── TLVectorDescriptor.kt │ ├── element │ ├── TLBoolean.kt │ ├── TLBytes.kt │ ├── TLCallable.kt │ ├── TLConstructor.kt │ ├── TLDouble.kt │ ├── TLElement.kt │ ├── TLExpression.kt │ ├── TLFalse.kt │ ├── TLFunction.kt │ ├── TLInt.kt │ ├── TLInt128.kt │ ├── TLInt32.kt │ ├── TLInt64.kt │ ├── TLNull.kt │ ├── TLPrimitive.kt │ ├── TLString.kt │ ├── TLTrue.kt │ └── TLVector.kt │ └── encoder │ └── ToByteArray.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs ├── stdlib-extensions │ ├── build.gradle.kts │ └── src │ │ └── commonMain │ │ └── kotlin │ │ └── kotl │ │ └── stdlib │ │ ├── bytes │ │ ├── Int.kt │ │ ├── Long.kt │ │ └── Pad.kt │ │ └── int │ │ └── NearestMultiple.kt └── string-parser │ ├── build.gradle.kts │ └── src │ └── commonMain │ └── kotlin │ └── kotl │ └── parser │ ├── Any.kt │ ├── Colon.kt │ ├── Comma.kt │ ├── Consume.kt │ ├── Discard.kt │ ├── LineParser.kt │ ├── Many.kt │ ├── Map.kt │ ├── Parser.kt │ ├── ParserDSL.kt │ ├── ParserResult.kt │ ├── Safely.kt │ ├── Semicolon.kt │ ├── Take.kt │ ├── Whitespace.kt │ └── debug │ └── PrintDebugInfo.kt ├── schema ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── kotl │ │ └── schema │ │ ├── element │ │ ├── TLSchemaCallable.kt │ │ ├── TLSchemaComment.kt │ │ ├── TLSchemaElement.kt │ │ └── TLSchemaSectionDivider.kt │ │ ├── parser │ │ ├── SchemaParser.kt │ │ ├── comment │ │ │ ├── CommentParser.kt │ │ │ ├── multiline │ │ │ │ └── MultilineCommentParser.kt │ │ │ └── singleline │ │ │ │ └── SingleLineParser.kt │ │ ├── function │ │ │ └── FunctionParser.kt │ │ ├── line │ │ │ └── EmptyLineParser.kt │ │ ├── section │ │ │ └── SectionDividerParser.kt │ │ └── type │ │ │ └── TypeParser.kt │ │ ├── prettyPrint │ │ └── PrettyPrint.kt │ │ └── types │ │ ├── FunctionHash.kt │ │ ├── FunctionName.kt │ │ ├── NullabilityMarker.kt │ │ ├── Parameter.kt │ │ ├── ParameterName.kt │ │ ├── TypeArgument.kt │ │ ├── TypeName.kt │ │ ├── TypeParameter.kt │ │ └── TypeReference.kt │ └── jvmMain │ └── kotlin │ └── Main.kt ├── serialization ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── kotl │ │ └── serialization │ │ ├── TL.kt │ │ ├── annotation │ │ ├── Crc32.kt │ │ ├── TLBare.kt │ │ ├── TLRpc.kt │ │ └── TLSize.kt │ │ ├── bare │ │ └── Bare.kt │ │ ├── bytes │ │ └── Bytes.kt │ │ ├── decoder │ │ ├── TLDecoder.kt │ │ └── TLElementReader.kt │ │ ├── encoder │ │ ├── TLElementWriter.kt │ │ └── TLEncoder.kt │ │ ├── extensions │ │ └── SerialDescriptor.kt │ │ └── int │ │ └── Int128.kt │ └── jvmMain │ └── kotlin │ ├── TLMain.kt │ ├── TLMain2.kt │ └── TLMain3.kt └── settings.gradle.kts /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Library Release Deploy 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | workflow_dispatch: 7 | 8 | env: 9 | GITHUB_USERNAME: ${{ github.actor }} 10 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 11 | 12 | jobs: 13 | 14 | deploy-multiplatform: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | packages: write 19 | outputs: 20 | release_version: ${{ steps.output_version.outputs.release_version }} 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Setup Java 24 | uses: actions/setup-java@v3 25 | with: 26 | distribution: temurin 27 | java-version: 11 28 | - name: Gradle Cache Setup 29 | uses: gradle/gradle-build-action@v2 30 | - name: Gradle Sync 31 | run: ./gradlew 32 | - name: Add Version to Env 33 | run: | 34 | release_version=$(./gradlew printVersion -q) 35 | echo "release_version=$release_version" >> $GITHUB_ENV 36 | - name: Publish ${{ env.release_version }} 37 | run: ./gradlew publishKotlinMultiplatformPublicationToGitHubRepository 38 | - name: Add Sdk Version to Output 39 | id: output_version 40 | run: echo "release_version=${{ env.release_version }}" >> $GITHUB_OUTPUT 41 | 42 | deploy-jvm: 43 | runs-on: ubuntu-latest 44 | permissions: 45 | contents: write 46 | packages: write 47 | steps: 48 | - uses: actions/checkout@v3 49 | - name: Setup Java 50 | uses: actions/setup-java@v3 51 | with: 52 | distribution: temurin 53 | java-version: 11 54 | - name: Gradle Cache Setup 55 | uses: gradle/gradle-build-action@v2 56 | - name: Gradle Sync 57 | run: ./gradlew 58 | - name: Add Version to Env 59 | run: | 60 | release_version=$(./gradlew printVersion -q) 61 | echo "release_version=$release_version" >> $GITHUB_ENV 62 | - name: Publish ${{ env.release_version }} 63 | run: ./gradlew publishJvmPublicationToGitHubRepository 64 | 65 | deploy-js: 66 | runs-on: ubuntu-latest 67 | permissions: 68 | contents: write 69 | packages: write 70 | steps: 71 | - uses: actions/checkout@v3 72 | - name: Setup Java 73 | uses: actions/setup-java@v3 74 | with: 75 | distribution: temurin 76 | java-version: 11 77 | - name: Gradle Cache Setup 78 | uses: gradle/gradle-build-action@v2 79 | - name: Gradle Sync 80 | run: ./gradlew 81 | - name: Add Version to Env 82 | run: | 83 | release_version=$(./gradlew printVersion -q) 84 | echo "release_version=$release_version" >> $GITHUB_ENV 85 | - name: Publish ${{ env.release_version }} 86 | run: ./gradlew publishJsPublicationToGitHubRepository 87 | 88 | deploy-konan: 89 | runs-on: macos-latest 90 | permissions: 91 | contents: write 92 | packages: write 93 | steps: 94 | - uses: actions/checkout@v3 95 | - name: Setup Java 96 | uses: actions/setup-java@v3 97 | with: 98 | distribution: temurin 99 | java-version: 11 100 | - name: Gradle Cache Setup 101 | uses: gradle/gradle-build-action@v2 102 | - name: Konan Cache Setup 103 | uses: actions/cache@v3 104 | with: 105 | path: ~/.konan 106 | key: konan-cache 107 | - name: Gradle Sync 108 | run: ./gradlew 109 | - name: Add Version to Env 110 | run: | 111 | release_version=$(./gradlew printVersion -q) 112 | echo "release_version=$release_version" >> $GITHUB_ENV 113 | - name: Publish ${{ env.release_version }} 114 | run: | 115 | ./gradlew publishIosX64PublicationToGitHubRepository \ 116 | publishIosSimulatorArm64PublicationToGitHubRepository \ 117 | publishIosArm64PublicationToGitHubRepository 118 | 119 | create-release: 120 | runs-on: ubuntu-latest 121 | permissions: 122 | contents: write 123 | packages: write 124 | needs: 125 | - deploy-multiplatform 126 | - deploy-jvm 127 | - deploy-js 128 | - deploy-konan 129 | steps: 130 | - name: Create Release 131 | uses: actions/create-release@v1 132 | with: 133 | tag_name: ${{ needs.deploy-multiplatform.outputs.release_version }} 134 | release_name: Release ${{ needs.deploy-multiplatform.outputs.release_version }} 135 | -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Library Snapshot Deploy 2 | 3 | on: 4 | push: 5 | branches-ignore: [ "master" ] 6 | workflow_dispatch: 7 | 8 | env: 9 | GITHUB_USERNAME: ${{ github.actor }} 10 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 11 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 12 | ORG_GRADLE_PROJECT_snapshot: true 13 | ORG_GRADLE_PROJECT_commit: ${{ github.sha }} 14 | ORG_GRADLE_PROJECT_attempt: ${{ github.run_attempt }} 15 | 16 | jobs: 17 | 18 | deploy-multiplatform: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | packages: write 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Gradle Cache Setup 26 | uses: gradle/gradle-build-action@v2 27 | with: 28 | cache-read-only: ${{ github.ref != 'refs/heads/dev' }} 29 | - name: Gradle Sync 30 | run: ./gradlew 31 | - name: Add Sdk Version to Env 32 | run: | 33 | snapshot_version=$(./gradlew printVersion -q) 34 | echo "snapshot_version=$snapshot_version" >> $GITHUB_ENV 35 | - name: Publish ${{ env.snapshot_version }} 36 | run: ./gradlew publishKotlinMultiplatformPublicationToGitHubRepository 37 | 38 | deploy-jvm: 39 | runs-on: ubuntu-latest 40 | permissions: 41 | contents: read 42 | packages: write 43 | steps: 44 | - uses: actions/checkout@v3 45 | - name: Gradle Cache Setup 46 | uses: gradle/gradle-build-action@v2 47 | with: 48 | cache-read-only: ${{ github.ref != 'refs/heads/dev' }} 49 | - name: Gradle Sync 50 | run: ./gradlew 51 | - name: Add Sdk Version to Env 52 | run: | 53 | snapshot_version=$(./gradlew printVersion -q) 54 | echo "snapshot_version=$snapshot_version" >> $GITHUB_ENV 55 | - name: Publish ${{ env.snapshot_version }} 56 | run: ./gradlew publishJvmPublicationToGitHubRepository 57 | 58 | deploy-js: 59 | runs-on: ubuntu-latest 60 | permissions: 61 | contents: read 62 | packages: write 63 | steps: 64 | - uses: actions/checkout@v3 65 | - name: Gradle Cache Setup 66 | uses: gradle/gradle-build-action@v2 67 | with: 68 | cache-read-only: ${{ github.ref != 'refs/heads/dev' }} 69 | - name: Gradle Sync 70 | run: ./gradlew 71 | - name: Add Sdk Version to Env 72 | run: | 73 | snapshot_version=$(./gradlew printVersion -q) 74 | echo "snapshot_version=$snapshot_version" >> $GITHUB_ENV 75 | - name: Publish ${{ env.snapshot_version }} 76 | run: ./gradlew publishJsPublicationToGitHubRepository 77 | 78 | deploy-konan: 79 | runs-on: macos-latest 80 | permissions: 81 | contents: read 82 | packages: write 83 | steps: 84 | - uses: actions/checkout@v3 85 | - name: Gradle Cache Setup 86 | uses: gradle/gradle-build-action@v2 87 | with: 88 | cache-read-only: ${{ github.ref != 'refs/heads/dev' }} 89 | - name: Konan Cache Setup 90 | uses: actions/cache@v3 91 | with: 92 | path: ~/.konan 93 | key: konan-cache 94 | - name: Gradle Sync 95 | run: ./gradlew 96 | - name: Add Sdk Version to Env 97 | run: | 98 | snapshot_version=$(./gradlew printVersion -q) 99 | echo "snapshot_version=$snapshot_version" >> $GITHUB_ENV 100 | - name: Publish ${{ env.snapshot_version }} 101 | run: | 102 | ./gradlew publishIosX64PublicationToGitHubRepository \ 103 | publishIosSimulatorArm64PublicationToGitHubRepository \ 104 | publishIosArm64PublicationToGitHubRepository -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | .gradle 4 | kotlin-js-store 5 | local.properties -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Alexander Sokolinsky 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # koTL 2 | 3 | [![GitHub stars](https://img.shields.io/github/stars/kotlin-telegram/koTL.svg?cache)](https://github.com/kotlin-telegram/koTL/stargazers) 4 | [![GitHub forks](https://img.shields.io/github/forks/kotlin-telegram/koTL.svg?cache)](https://github.com/kotlin-telegram/koTL/network) 5 | [![License](https://img.shields.io/github/license/kotlin-telegram/koTL.svg?cache)](LICENSE.md) 6 | 7 | ⚠️ ALL OF THIS IS WORK IN PROGRESS ⚠️ 8 | 9 | `koTL` is a Kotlin library that simplifies working with Telegram's Type Language (TL) by providing seamless integration with kotlinx.serialization. It offers tools and utilities for managing TL schema, serializing and deserializing TL objects, and extending kotlinx.serialization capabilities for Telegram-related applications. 10 | 11 | ## Features 12 | 13 | - TL schema parsing and management. 14 | - Serialization and deserialization of TL objects. 15 | - Seamless integration with kotlinx.serialization. 16 | - Kotlin DSL for constructing TL objects 17 | 18 | ## Example of Usage 19 | 20 | ```kotlin 21 | @Serializable 22 | @Crc32(value = 0x2d84d5f5_u) 23 | data class GetUserRequest( 24 | val ids: List 25 | ) 26 | 27 | @Serializable 28 | sealed interface InputUserType 29 | 30 | @Serializable 31 | @Crc32(value = 0xb98886cf_u) 32 | data object InputUserEmpty : InputUserType 33 | 34 | @Serializable 35 | @Crc32(value = 0xf7c1b13f_u) 36 | data object InputUserSelf : InputUserType 37 | 38 | @Serializable 39 | @Crc32(value = 0xf21158c6_u) 40 | data class InputUser( 41 | val userId: Long, 42 | val accessHash: Long 43 | ) : InputUserType 44 | ``` 45 | -------------------------------------------------------------------------------- /build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | `kotlin-dsl` 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | google() 9 | gradlePluginPortal() 10 | } 11 | 12 | dependencies { 13 | api(libs.kotlinPlugin) 14 | api(libs.kotlinxSerializationPlugin) 15 | } 16 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories { 3 | mavenCentral() 4 | google() 5 | } 6 | 7 | versionCatalogs { 8 | create("libs") { 9 | from(files("../gradle/libs.versions.toml")) 10 | } 11 | } 12 | } 13 | 14 | include(":library-deploy") 15 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/Version.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Project 2 | 3 | fun Project.versionFromProperties(acceptor: (String) -> Unit) { 4 | afterEvaluate { 5 | acceptor(versionFromProperties()) 6 | } 7 | } 8 | 9 | fun Project.versionFromProperties(): String { 10 | val snapshot = project.findProperty("snapshot")?.toString()?.toBooleanStrict() 11 | if (snapshot == null || !snapshot) return project.version.toString() 12 | 13 | val commit = project.property("commit").toString() 14 | val attempt = project.property("attempt").toString().toInt() 15 | 16 | val version = buildString { 17 | append(project.version) 18 | append("-build") 19 | append(commit.take(n = 7)) 20 | if (attempt > 1) { 21 | append(attempt) 22 | } 23 | } 24 | 25 | return version 26 | } 27 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/kmp-library-convention.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNUSED_VARIABLE") 2 | 3 | plugins { 4 | kotlin("multiplatform") 5 | kotlin("plugin.serialization") 6 | id("publication-convention") 7 | } 8 | 9 | kotlin { 10 | jvm { 11 | jvmToolchain(8) 12 | } 13 | js(IR) { 14 | browser() 15 | nodejs() 16 | } 17 | iosArm64() 18 | iosX64() 19 | iosSimulatorArm64() 20 | 21 | explicitApi() 22 | 23 | sourceSets { 24 | val commonMain by getting 25 | val commonTest by getting 26 | 27 | val iosX64Main by getting 28 | val iosArm64Main by getting 29 | val iosSimulatorArm64Main by getting 30 | 31 | val iosMain by creating { 32 | dependsOn(commonMain) 33 | iosX64Main.dependsOn(this) 34 | iosArm64Main.dependsOn(this) 35 | iosSimulatorArm64Main.dependsOn(this) 36 | } 37 | 38 | val iosX64Test by getting 39 | val iosArm64Test by getting 40 | val iosSimulatorArm64Test by getting 41 | val iosTest by creating { 42 | dependsOn(commonTest) 43 | iosX64Test.dependsOn(this) 44 | iosArm64Test.dependsOn(this) 45 | iosSimulatorArm64Test.dependsOn(this) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/print-sdk-version-convention.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNUSED_VARIABLE") 2 | 3 | import org.gradle.kotlin.dsl.creating 4 | 5 | tasks { 6 | val printVersion by creating { 7 | group = "CI" 8 | 9 | doFirst { 10 | println(versionFromProperties()) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/publication-convention.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.maven-publish") 3 | } 4 | 5 | group = "me.y9san9.kotl" 6 | 7 | publishing { 8 | repositories { 9 | maven { 10 | name = "GitHub" 11 | url = uri("https://maven.pkg.github.com/kotlin-telegram/koTL") 12 | credentials { 13 | username = System.getenv("GITHUB_USERNAME") 14 | password = System.getenv("GITHUB_TOKEN") 15 | } 16 | } 17 | } 18 | 19 | publications.withType { 20 | versionFromProperties { version -> 21 | this.version = version 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("print-sdk-version-convention") 3 | } 4 | 5 | version = libs.versions.koTLVersion.get() 6 | -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kmp-library-convention") 3 | id("publication-convention") 4 | } 5 | 6 | version = libs.versions.koTLVersion.get() 7 | 8 | dependencies { 9 | commonMainImplementation(projects.libs.stdlibExtensions) 10 | } 11 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/annotation/TLDSL.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.annotation 2 | 3 | @DslMarker 4 | public annotation class TLDsl 5 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/builder/BuildTLCallable.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.builder 2 | 3 | import kotl.core.annotation.TLDsl 4 | import kotl.core.element.TLConstructor 5 | import kotl.core.element.TLExpression 6 | import kotl.core.element.TLFunction 7 | import kotl.core.element.typedLanguage 8 | 9 | @TLDsl 10 | public class TLCallableBuilder( 11 | private val crc32: UInt? = null 12 | ) { 13 | private val parameters = mutableListOf() 14 | 15 | public fun parameter(value: TLExpression) { 16 | parameters += value 17 | } 18 | 19 | public fun parameter(boolean: Boolean): Unit = parameter(boolean.typedLanguage) 20 | public fun parameter(double: Double): Unit = parameter(double.typedLanguage) 21 | public fun parameter(int: Int): Unit = parameter(int.typedLanguage) 22 | public fun parameter(long: Long): Unit = parameter(long.typedLanguage) 23 | public fun parameter(nothing: Nothing?): Unit = parameter(nothing.typedLanguage) 24 | public fun parameter(string: String): Unit = parameter(string.typedLanguage) 25 | 26 | public inline fun constructorParameter(crc32: UInt, builder: TLCallableBuilder.() -> Unit = {}) { 27 | parameter(buildTLConstructor(crc32, builder)) 28 | } 29 | public inline fun vectorParameter(builder: TLVectorBuilder.() -> Unit) { 30 | parameter(buildTLVector(builder)) 31 | } 32 | 33 | public fun toFunction(): TLFunction = TLFunction( 34 | crc32 = crc32 ?: error("Cannot create a function without crc32 set"), 35 | parameters = parameters.toList() 36 | ) 37 | 38 | public fun toConstructor(): TLConstructor = 39 | if (crc32 == null) { 40 | TLConstructor.Bare(parameters) 41 | } else { 42 | TLConstructor.Boxed(crc32, parameters) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/builder/BuildTLConstructor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.builder 2 | 3 | import kotl.core.element.TLConstructor 4 | 5 | public inline fun buildTLConstructor( 6 | crc32: UInt? = null, 7 | builder: TLCallableBuilder.() -> Unit = {} 8 | ): TLConstructor = TLCallableBuilder(crc32).apply(builder).toConstructor() 9 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/builder/BuildTLConstructorDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.builder 2 | 3 | import kotl.core.descriptor.* 4 | 5 | public class TLConstructorDescriptorBuilder( 6 | private val crc32: UInt 7 | ) { 8 | private val expressions = mutableListOf() 9 | 10 | public fun parameter(expression: TLExpressionDescriptor) { 11 | expressions += expression 12 | } 13 | 14 | public fun booleanParameter(): Unit = parameter(TLBooleanDescriptor) 15 | public fun doubleParameter(): Unit = parameter(TLDoubleDescriptor) 16 | public fun int32Parameter(): Unit = parameter(TLInt32Descriptor) 17 | public fun int64Parameter(): Unit = parameter(TLInt64Descriptor) 18 | public fun int128Parameter(): Unit = parameter(TLInt128Descriptor) 19 | public fun stringParameter(): Unit = parameter(TLStringDescriptor) 20 | public fun nullParameter(): Unit = parameter(TLNullDescriptor) 21 | 22 | public fun parameter(block: TLTypeDescriptorBuilder.() -> Unit) { 23 | parameter(buildTLTypeDescriptor(block)) 24 | } 25 | 26 | public fun vectorParameter(block: TLVectorDescriptorBuilder.() -> Unit) { 27 | parameter(buildTLVectorDescriptor(block)) 28 | } 29 | 30 | public fun build(): TLConstructorDescriptor = TLConstructorDescriptor(crc32, expressions) 31 | } 32 | 33 | public inline fun buildTLConstructorDescriptor( 34 | crc32: UInt, 35 | block: TLConstructorDescriptorBuilder.() -> Unit = {} 36 | ): TLConstructorDescriptor = TLConstructorDescriptorBuilder(crc32).apply(block).build() 37 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/builder/BuildTLFunction.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.builder 2 | 3 | import kotl.core.element.TLFunction 4 | 5 | public inline fun buildTLFunction( 6 | crc32: UInt, 7 | block: TLCallableBuilder.() -> Unit = {} 8 | ): TLFunction = TLCallableBuilder(crc32).apply(block).toFunction() 9 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/builder/BuildTLTypeDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.builder 2 | 3 | import kotl.core.descriptor.TLConstructorDescriptor 4 | import kotl.core.descriptor.TLTypeDescriptor 5 | 6 | public class TLTypeDescriptorBuilder { 7 | private val constructors = mutableListOf() 8 | 9 | public fun constructor(value: TLConstructorDescriptor) { 10 | constructors += value 11 | } 12 | 13 | public fun constructor( 14 | crc32: UInt, 15 | block: TLConstructorDescriptorBuilder.() -> Unit = {} 16 | ) { 17 | constructor(buildTLConstructorDescriptor(crc32, block)) 18 | } 19 | 20 | public fun build(): TLTypeDescriptor { 21 | require(constructors.isNotEmpty()) { "Cannot create a type descriptor without any constructors" } 22 | return TLTypeDescriptor(constructors) 23 | } 24 | } 25 | 26 | public inline fun buildTLTypeDescriptor( 27 | block: TLTypeDescriptorBuilder.() -> Unit 28 | ): TLTypeDescriptor = TLTypeDescriptorBuilder().apply(block).build() 29 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/builder/BuildTLVector.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.builder 2 | 3 | import kotl.core.annotation.TLDsl 4 | import kotl.core.element.TLElement 5 | import kotl.core.element.TLExpression 6 | import kotl.core.element.TLVector 7 | import kotl.core.element.typedLanguage 8 | 9 | @TLDsl 10 | public class TLVectorBuilder { 11 | private val elements = mutableListOf() 12 | 13 | public fun add(element: TLExpression) { 14 | elements += element 15 | } 16 | 17 | public fun add(boolean: Boolean): Unit = add(boolean.typedLanguage) 18 | public fun add(double: Double): Unit = add(double.typedLanguage) 19 | public fun add(int: Int): Unit = add(int.typedLanguage) 20 | public fun add(long: Long): Unit = add(long.typedLanguage) 21 | public fun add(nothing: Nothing?): Unit = add(nothing.typedLanguage) 22 | public fun add(string: String): Unit = add(string.typedLanguage) 23 | 24 | public inline fun addFunctionCall( 25 | crc32: UInt, 26 | block: TLCallableBuilder.() -> Unit = {} 27 | ) { 28 | add(buildTLConstructor(crc32, block)) 29 | } 30 | public inline fun addVector(block: TLVectorBuilder.() -> Unit) { 31 | add(buildTLVector(block)) 32 | } 33 | 34 | public fun build(): TLVector = TLVector(elements.toList()) 35 | } 36 | 37 | public inline fun buildTLVector( 38 | block: TLVectorBuilder.() -> Unit 39 | ): TLVector = TLVectorBuilder().apply(block).build() 40 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/builder/BuildTLVectorDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.builder 2 | 3 | import kotl.core.descriptor.* 4 | 5 | public class TLVectorDescriptorBuilder { 6 | private var underlying: TLExpressionDescriptor? = null 7 | 8 | public fun underlying(expression: TLExpressionDescriptor) { 9 | underlying = expression 10 | } 11 | 12 | public fun booleanUnderlying(): Unit = underlying(TLBooleanDescriptor) 13 | public fun doubleUnderlying(): Unit = underlying(TLDoubleDescriptor) 14 | public fun int32Underlying(): Unit = underlying(TLInt32Descriptor) 15 | public fun int64Underlying(): Unit = underlying(TLInt64Descriptor) 16 | public fun int128Underlying(): Unit = underlying(TLInt128Descriptor) 17 | public fun stringUnderlying(): Unit = underlying(TLStringDescriptor) 18 | public fun nullUnderlying(): Unit = underlying(TLNullDescriptor) 19 | 20 | public fun underlying(block: TLTypeDescriptorBuilder.() -> Unit) { 21 | underlying(buildTLTypeDescriptor(block)) 22 | } 23 | public fun underlyingVector(block: TLVectorDescriptorBuilder.() -> Unit) { 24 | underlying(buildTLVectorDescriptor(block)) 25 | } 26 | 27 | public fun build(): TLVectorDescriptor = TLVectorDescriptor( 28 | underlying = underlying ?: error("You should call `underlying` function in order to build TLVectorDescriptor") 29 | ) 30 | } 31 | 32 | public inline fun buildTLVectorDescriptor( 33 | block: TLVectorDescriptorBuilder.() -> Unit 34 | ): TLVectorDescriptor = TLVectorDescriptorBuilder().apply(block).build() 35 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/decoder/ByteArrayInput.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.decoder 2 | 3 | import kotl.core.descriptor.TLStringDescriptor.STRING_SIZE_MAGIC_NUMBER 4 | import kotl.stdlib.bytes.decodeInt 5 | import kotl.stdlib.bytes.decodeLong 6 | import kotl.stdlib.int.nearestMultipleOf 7 | 8 | internal class ByteArrayInput(private val bytes: ByteArray) { 9 | private var position: Int = 0 10 | 11 | fun scanInt(): Int = bytes.decodeInt(position) 12 | 13 | fun readInt(): Int { 14 | val int = scanInt() 15 | position += Int.SIZE_BYTES 16 | return int 17 | } 18 | 19 | fun readLong(): Long { 20 | val long = bytes.decodeLong(position) 21 | position += Long.SIZE_BYTES 22 | return long 23 | } 24 | 25 | fun readDouble(): Double { 26 | val double = Double.fromBits(readLong()) 27 | return double 28 | } 29 | 30 | fun readString(): String = readBytes().decodeToString() 31 | 32 | fun readBytes(): ByteArray { 33 | val head = bytes[position] 34 | position++ 35 | return if (head.toUByte() == STRING_SIZE_MAGIC_NUMBER) { 36 | readLongString() 37 | } else { 38 | readShortString(head) 39 | } 40 | } 41 | 42 | private fun readLongString(): ByteArray { 43 | val sizeBytes = bytes.sliceArray(position..(position + 2)) + 0 44 | val size = sizeBytes.decodeInt(offset = 0) 45 | position += (Int.SIZE_BYTES - 1) // one was already read for head 46 | val bytes = ByteArray(size) { i -> bytes[position + i] } 47 | position += size 48 | return bytes 49 | } 50 | 51 | private fun readShortString(head: Byte): ByteArray { 52 | val size = head.toInt() and 0xff 53 | val bytes = ByteArray(size) { i -> bytes[position + i] } 54 | 55 | val sizeWithHead = size + 1 56 | val sizeWithPadding = sizeWithHead.nearestMultipleOf(n = 4) - 1 57 | position += sizeWithPadding 58 | 59 | return bytes 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/decoder/FromByteArray.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.decoder 2 | 3 | import kotl.core.descriptor.* 4 | import kotl.core.element.* 5 | 6 | public fun TLExpressionDescriptor.decodeFromByteArray(byteArray: ByteArray): TLExpression = 7 | decodeFromBytes(ByteArrayInput(byteArray)) 8 | 9 | public fun TLTypeDescriptor.decodeFromByteArray(byteArray: ByteArray): TLConstructor = 10 | decodeFromBytes(ByteArrayInput(byteArray)) 11 | 12 | public fun TLVectorDescriptor.decodeFromByteArray(byteArray: ByteArray): TLVector = 13 | decodeFromBytes(ByteArrayInput(byteArray)) 14 | 15 | private fun TLExpressionDescriptor.decodeFromBytes(input: ByteArrayInput): TLExpression = 16 | when (this) { 17 | is TLTypeDescriptor -> decodeFromBytes(input) 18 | is TLVectorDescriptor -> decodeFromBytes(input) 19 | is TLBooleanDescriptor -> decodeFromBytes(input) 20 | is TLNullDescriptor -> decodeFromBytes(input) 21 | is TLIntDescriptor -> decodeFromBytes(input) 22 | is TLDoubleDescriptor -> TLDouble(input.readDouble()) 23 | is TLStringDescriptor -> TLString(input.readString()) 24 | is TLBytesDescriptor -> TLBytes(input.readBytes()) 25 | } 26 | 27 | private fun TLVectorDescriptor.decodeFromBytes( 28 | input: ByteArrayInput 29 | ): TLVector { 30 | val crc32 = input.readInt().toUInt() 31 | require(crc32 == TLVector.CRC32) { "CRC32 Mismatch: Trying to decode #${TLVector.CRC32.toString(radix = 16)} (vector), but got #${crc32.toString(radix = 16)}" } 32 | val size = input.readInt() 33 | val elements = List(size) { underlying.decodeFromBytes(input) } 34 | return TLVector(elements) 35 | } 36 | 37 | private fun TLTypeDescriptor.decodeFromBytes( 38 | input: ByteArrayInput 39 | ): TLConstructor { 40 | val crc32: UInt? 41 | 42 | val parametersDescriptors: List 43 | 44 | when (this) { 45 | is TLTypeDescriptor.Bare -> { 46 | crc32 = null 47 | parametersDescriptors = parameters 48 | } 49 | is TLTypeDescriptor.Boxed -> { 50 | crc32 = input.readInt().toUInt() 51 | 52 | val constructor = constructors 53 | .firstOrNull { constructor -> constructor.crc32 == crc32 } 54 | ?: error("CRC32 Mismatch: Trying to decode type with constructors #${constructors.map { it.crc32.toString(radix = 16) }}, but got #${crc32.toString(radix = 16)}") 55 | 56 | parametersDescriptors = constructor.parameters 57 | } 58 | } 59 | 60 | val parameters = parametersDescriptors.map { parameter -> 61 | parameter.decodeFromBytes(input) 62 | } 63 | 64 | return TLConstructor(crc32, parameters) 65 | } 66 | 67 | @Suppress("UnusedReceiverParameter") 68 | private fun TLNullDescriptor.decodeFromBytes(input: ByteArrayInput): TLNull { 69 | val crc32 = input.readInt().toUInt() 70 | require(crc32 == TLNull.CRC32) { "CRC32 Mismatch: Trying to decode #${TLNull.CRC32.toString(radix = 16)} (Null), but got #${crc32.toString(radix = 16)}" } 71 | return TLNull 72 | } 73 | 74 | private fun TLBooleanDescriptor.decodeFromBytes(input: ByteArrayInput): TLBoolean { 75 | return when (val crc32 = input.readInt().toUInt()) { 76 | TLTrue.CRC32 -> TLTrue 77 | TLFalse.CRC32 -> TLFalse 78 | else -> error("CRC32 Mismatch: Trying to decode type with constructors #${constructors.map { it.crc32.toString(radix = 16) }} (Boolean), but got #${crc32.toString(radix = 16)}") 79 | } 80 | } 81 | 82 | private fun TLIntDescriptor.decodeFromBytes(input: ByteArrayInput): TLInt { 83 | val size = sizeBytes / Int.SIZE_BYTES 84 | val data = IntArray(size) { input.readInt() } 85 | return create(data) 86 | } 87 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLBooleanDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | import kotl.core.element.TLFalse 4 | import kotl.core.element.TLTrue 5 | 6 | public data object TLBooleanDescriptor : TLPrimitiveDescriptor { 7 | public val constructors: List = listOf( 8 | TLTrueConstructorDescriptor, 9 | TLFalseConstructorDescriptor 10 | ) 11 | public fun asTypeDescriptor(): TLTypeDescriptor = TLTypeDescriptor(constructors) 12 | } 13 | 14 | public val TLTrueConstructorDescriptor: TLConstructorDescriptor = 15 | TLConstructorDescriptor(TLTrue.CRC32, emptyList()) 16 | 17 | public val TLFalseConstructorDescriptor: TLConstructorDescriptor = 18 | TLConstructorDescriptor(TLFalse.CRC32, emptyList()) 19 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLBytesDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | public data object TLBytesDescriptor : TLPrimitiveDescriptor 4 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLConstructorDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | public data class TLConstructorDescriptor( 4 | val crc32: UInt, 5 | val parameters: List 6 | ) 7 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLDoubleDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | public data object TLDoubleDescriptor : TLPrimitiveDescriptor 4 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLExpressionDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | public sealed interface TLExpressionDescriptor 4 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLInt128Descriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | import kotl.core.element.TLInt128 4 | 5 | public data object TLInt128Descriptor : TLIntDescriptor { 6 | override val sizeBytes: Int = 16 7 | override fun create(data: IntArray): TLInt128 = TLInt128(data) 8 | } 9 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLInt32Descriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | import kotl.core.element.TLInt32 4 | 5 | public data object TLInt32Descriptor : TLIntDescriptor { 6 | override val sizeBytes: Int = TLInt32.SIZE_BYTES 7 | override fun create(data: IntArray): TLInt32 = TLInt32.of(data) 8 | } 9 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLInt64Descriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | import kotl.core.element.TLInt64 4 | 5 | public data object TLInt64Descriptor : TLIntDescriptor { 6 | override val sizeBytes: Int = TLInt64.SIZE_BYTES 7 | override fun create(data: IntArray): TLInt64 = TLInt64.of(data) 8 | } 9 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLIntDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | import kotl.core.element.TLInt 4 | 5 | public sealed interface TLIntDescriptor : TLPrimitiveDescriptor { 6 | public val sizeBytes: Int 7 | 8 | public fun create(data: IntArray): TLInt 9 | 10 | public companion object { 11 | public fun of(sizeBits: Int): TLIntDescriptor = when (sizeBits) { 12 | 32 -> TLInt32Descriptor 13 | 64 -> TLInt64Descriptor 14 | 128 -> TLInt128Descriptor 15 | else -> error("Unsupported Int size") 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLNullDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | import kotl.core.element.TLNull 4 | 5 | public data object TLNullDescriptor : TLPrimitiveDescriptor { 6 | public fun asTypeDescriptor(): TLTypeDescriptor = 7 | TLTypeDescriptor(listOf(TLNullConstructorDescriptor)) 8 | } 9 | 10 | public val TLNullConstructorDescriptor: TLConstructorDescriptor = 11 | TLConstructorDescriptor(TLNull.CRC32, emptyList()) 12 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLPrimitiveDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | public sealed interface TLPrimitiveDescriptor : TLExpressionDescriptor 4 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLStringDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | public data object TLStringDescriptor : TLPrimitiveDescriptor { 4 | /** 5 | * The values of type string look differently depending on the 6 | * length L of the string being serialized: 7 | * 8 | * If L <= 253, the serialization contains one byte with the value of L, 9 | * then L bytes of the string followed by 0 to 3 characters containing 0, 10 | * such that the overall length of the value be divisible by 4, whereupon 11 | * all of this is interpreted as a sequence of int(L/4)+1 32-bit numbers. 12 | * 13 | * If L >= 254, the serialization contains byte 254, followed by 3 bytes 14 | * with the string length L, followed by L bytes of the string, 15 | * further followed by 0 to 3 null padding bytes. 16 | */ 17 | public const val STRING_SIZE_MAGIC_NUMBER: UByte = 254_u 18 | } 19 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLTypeDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | public sealed interface TLTypeDescriptor : TLExpressionDescriptor { 4 | public data class Boxed(val constructors: List) : TLTypeDescriptor 5 | public data class Bare(val parameters: List) : TLTypeDescriptor 6 | } 7 | 8 | public fun TLTypeDescriptor(constructors: List): TLTypeDescriptor = 9 | TLTypeDescriptor.Boxed(constructors) 10 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/descriptor/TLVectorDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.descriptor 2 | 3 | public data class TLVectorDescriptor(val underlying: TLExpressionDescriptor) : TLExpressionDescriptor 4 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLBoolean.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public sealed interface TLBoolean : TLPrimitive { 4 | public val boolean: Boolean 5 | } 6 | 7 | public val Boolean.typedLanguage: TLBoolean get() = when (this) { 8 | true -> TLTrue 9 | false -> TLFalse 10 | } 11 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLBytes.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class TLBytes( 7 | public val bytes: ByteArray 8 | ) : TLPrimitive 9 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLCallable.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public sealed interface TLCallable : TLElement { 4 | public val crc32: UInt? 5 | public val parameters: List 6 | } 7 | 8 | public val TLCallable.bare: Boolean get() = crc32 == null 9 | public val TLCallable.boxed: Boolean get() = crc32 != null 10 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLConstructor.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public sealed interface TLConstructor : TLCallable, TLExpression { 4 | public data class Boxed( 5 | override val crc32: UInt, 6 | override val parameters: List 7 | ) : TLConstructor 8 | 9 | public data class Bare( 10 | override val parameters: List 11 | ) : TLConstructor { 12 | override val crc32: UInt? = null 13 | } 14 | } 15 | 16 | public fun TLConstructor(crc32: UInt?, parameters: List): TLConstructor { 17 | if (crc32 == null) return TLConstructor.Bare(parameters) 18 | return TLConstructor.Boxed(crc32, parameters) 19 | } 20 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLDouble.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class TLDouble(public val double: Double) : TLPrimitive 7 | 8 | public val Double.typedLanguage: TLDouble get() = TLDouble(double = this) 9 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLElement.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public sealed interface TLElement { 4 | public companion object 5 | } 6 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLExpression.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | /** 4 | * Elements that can be passed to function as parameters (everything except functions) 5 | */ 6 | public sealed interface TLExpression : TLElement 7 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLFalse.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public data object TLFalse : TLBoolean { 4 | override val boolean: Boolean = false 5 | 6 | public fun asTLConstructor(): TLConstructor = TLConstructor(CRC32, emptyList()) 7 | 8 | public const val CRC32: UInt = 0xbc799737_u 9 | } 10 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLFunction.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public data class TLFunction( 4 | override val crc32: UInt, 5 | override val parameters: List 6 | ) : TLCallable 7 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLInt.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public sealed interface TLInt : TLExpression { 4 | public val data: IntArray 5 | } 6 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLInt128.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class TLInt128(override val data: IntArray) : TLInt { 7 | init { 8 | require(data.size == SIZE_BYTES / Int.SIZE_BYTES) 9 | } 10 | 11 | public companion object { 12 | public const val SIZE_BYTES: Int = 16 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLInt32.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class TLInt32(public val int: Int) : TLInt { 7 | override val data: IntArray get() = intArrayOf(int) 8 | 9 | public companion object { 10 | public const val SIZE_BYTES: Int = Int.SIZE_BYTES 11 | public fun of(data: IntArray): TLInt32 = TLInt32(data[0]) 12 | } 13 | } 14 | 15 | public val Int.typedLanguage: TLInt32 get() = TLInt32(int = this) 16 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLInt64.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class TLInt64(public val long: Long) : TLInt { 7 | override val data: IntArray get() { 8 | val lowerInt = long.toInt() 9 | val upperInt = (long shr 32).toInt() 10 | return intArrayOf(lowerInt, upperInt) 11 | } 12 | 13 | public companion object { 14 | public const val SIZE_BYTES: Int = Long.SIZE_BYTES 15 | public fun of(data: IntArray): TLInt64 { 16 | val long = (data[0].toLong() and 0xFFFFFFFF) or 17 | (data[1].toLong() and 0xFFFFFFFF shl 32) 18 | return TLInt64(long) 19 | } 20 | } 21 | } 22 | 23 | public val Long.typedLanguage: TLInt64 get() = TLInt64(long = this) 24 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLNull.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public data object TLNull : TLPrimitive { 4 | public fun asTLConstructor(): TLConstructor = TLConstructor(CRC32, emptyList()) 5 | 6 | public const val CRC32: UInt = 0x56730bcc_u 7 | } 8 | 9 | @Suppress("UnusedReceiverParameter") 10 | public val Nothing?.typedLanguage: TLNull get() = TLNull 11 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLPrimitive.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public sealed interface TLPrimitive : TLExpression 4 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLString.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class TLString( 7 | public val string: String 8 | ) : TLPrimitive 9 | 10 | public val String.typedLanguage: TLString get() = TLString(string = this) 11 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLTrue.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public data object TLTrue : TLBoolean { 4 | override val boolean: Boolean = true 5 | 6 | public fun asTLConstructor(): TLConstructor = TLConstructor(CRC32, emptyList()) 7 | 8 | public const val CRC32: UInt = 0x997275b5_u 9 | } 10 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/element/TLVector.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.element 2 | 3 | public data class TLVector(val elements: List) : TLExpression { 4 | public fun asTLConstructor(): TLConstructor = 5 | TLConstructor( 6 | crc32 = CRC32, 7 | parameters = listOf(elements.size.typedLanguage) + elements 8 | ) 9 | 10 | public companion object { 11 | public const val CRC32: UInt = 0x1cb5c415_u 12 | 13 | public val Empty: TLVector = TLVector(emptyList()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/kotl/core/encoder/ToByteArray.kt: -------------------------------------------------------------------------------- 1 | package kotl.core.encoder 2 | 3 | import kotl.core.descriptor.TLStringDescriptor.STRING_SIZE_MAGIC_NUMBER 4 | import kotl.core.element.* 5 | import kotl.stdlib.bytes.encodeToByteArray 6 | import kotl.stdlib.bytes.padEnd 7 | import kotl.stdlib.int.nearestMultipleOf 8 | 9 | public fun TLElement.encodeToByteArray(): ByteArray = when (this) { 10 | is TLConstructor -> encodeToByteArray() 11 | is TLFunction -> asConstructor().encodeToByteArray() 12 | is TLInt -> encodeToByteArray() 13 | is TLDouble -> double.toBits().encodeToByteArray() 14 | is TLString -> string.encodeStringTL() 15 | is TLBytes -> bytes.encodeBytesTL() 16 | is TLVector -> asTLConstructor().encodeToByteArray() 17 | is TLTrue -> asTLConstructor().encodeToByteArray() 18 | is TLFalse -> asTLConstructor().encodeToByteArray() 19 | is TLNull -> asTLConstructor().encodeToByteArray() 20 | } 21 | 22 | private fun TLConstructor.encodeToByteArray(): ByteArray { 23 | val header = crc32?.toInt()?.encodeToByteArray() ?: byteArrayOf() 24 | val bytes = parameters.fold(header) { data, element -> 25 | data + element.encodeToByteArray() 26 | } 27 | return bytes 28 | } 29 | 30 | // fixme: pretty slow and memory-consuming 31 | private fun TLInt.encodeToByteArray(): ByteArray { 32 | return data.fold(byteArrayOf()) { acc, int -> 33 | acc + int.encodeToByteArray() 34 | } 35 | } 36 | 37 | private fun TLFunction.asConstructor(): TLConstructor = TLConstructor(crc32, parameters) 38 | 39 | private fun String.encodeStringTL(): ByteArray = 40 | encodeToByteArray().encodeBytesTL() 41 | 42 | private fun ByteArray.encodeBytesTL(): ByteArray = 43 | if (size < STRING_SIZE_MAGIC_NUMBER.toInt()) { 44 | encodeSmallString() 45 | } else { 46 | encodeBigString() 47 | } 48 | 49 | private fun ByteArray.encodeSmallString(): ByteArray { 50 | val unpadded = byteArrayOf(size.toByte()) + this 51 | val desiredLength = unpadded.size.nearestMultipleOf(n = 4) 52 | return unpadded.padEnd(desiredLength) 53 | } 54 | 55 | private fun ByteArray.encodeBigString(): ByteArray { 56 | val unpadded = byteArrayOf(STRING_SIZE_MAGIC_NUMBER.toByte()) + 57 | size.encodeToByteArray().dropLast(n = 1) + 58 | this 59 | 60 | val desiredLength = unpadded.size.nearestMultipleOf(n = 4) 61 | 62 | return unpadded.padEnd(desiredLength) 63 | } 64 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.js.compiler=ir 3 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | kotlin = "1.9.0" 4 | koTLVersion = "0.0.15" 5 | kotlinxSerialization = "1.5.0" 6 | 7 | [libraries] 8 | 9 | kotlinxSerialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerialization" } 10 | 11 | # Gradle plugins 12 | kotlinxSerializationPlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } 13 | kotlinPlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kotlin-telegram/koTL/7cabb4c37ae9b138b2ce306478b083675e0ad6c4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 134 | 135 | Please set the JAVA_HOME variable in your environment to match the 136 | location of your Java installation." 137 | fi 138 | 139 | # Increase the maximum file descriptors if we can. 140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 141 | case $MAX_FD in #( 142 | max*) 143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 144 | # shellcheck disable=SC3045 145 | MAX_FD=$( ulimit -H -n ) || 146 | warn "Could not query maximum file descriptor limit" 147 | esac 148 | case $MAX_FD in #( 149 | '' | soft) :;; #( 150 | *) 151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 152 | # shellcheck disable=SC3045 153 | ulimit -n "$MAX_FD" || 154 | warn "Could not set maximum file descriptor limit to $MAX_FD" 155 | esac 156 | fi 157 | 158 | # Collect all arguments for the java command, stacking in reverse order: 159 | # * args from the command line 160 | # * the main class name 161 | # * -classpath 162 | # * -D...appname settings 163 | # * --module-path (only if needed) 164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 165 | 166 | # For Cygwin or MSYS, switch paths to Windows format before running java 167 | if "$cygwin" || "$msys" ; then 168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 170 | 171 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 172 | 173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 174 | for arg do 175 | if 176 | case $arg in #( 177 | -*) false ;; # don't mess with options #( 178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 179 | [ -e "$t" ] ;; #( 180 | *) false ;; 181 | esac 182 | then 183 | arg=$( cygpath --path --ignore --mixed "$arg" ) 184 | fi 185 | # Roll the args list around exactly as many times as the number of 186 | # args, so each arg winds up back in the position where it started, but 187 | # possibly modified. 188 | # 189 | # NB: a `for` loop captures its iteration list before it begins, so 190 | # changing the positional parameters here affects neither the number of 191 | # iterations, nor the values presented in `arg`. 192 | shift # remove old arg 193 | set -- "$@" "$arg" # push replacement arg 194 | done 195 | fi 196 | 197 | 198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 200 | 201 | # Collect all arguments for the java command; 202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 203 | # shell script including quotes and variable substitutions, so put them in 204 | # double quotes to make sure that they get re-expanded; and 205 | # * put everything else in single quotes, so that it's not re-expanded. 206 | 207 | set -- \ 208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 209 | -classpath "$CLASSPATH" \ 210 | org.gradle.wrapper.GradleWrapperMain \ 211 | "$@" 212 | 213 | # Stop when "xargs" is not available. 214 | if ! command -v xargs >/dev/null 2>&1 215 | then 216 | die "xargs is not available" 217 | fi 218 | 219 | # Use "xargs" to parse quoted args. 220 | # 221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 222 | # 223 | # In Bash we could simply go: 224 | # 225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 226 | # set -- "${ARGS[@]}" "$@" 227 | # 228 | # but POSIX shell has neither arrays nor command substitution, so instead we 229 | # post-process each arg (as a line of input to sed) to backslash-escape any 230 | # character that might be a shell metacharacter, then use eval to reverse 231 | # that process (while maintaining the separation between arguments), and wrap 232 | # the whole thing up as a single "set" statement. 233 | # 234 | # This will of course break if any of these variables contains a newline or 235 | # an unmatched quote. 236 | # 237 | 238 | eval "set -- $( 239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 240 | xargs -n1 | 241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 242 | tr '\n' ' ' 243 | )" '"$@"' 244 | 245 | exec "$JAVACMD" "$@" 246 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /libs/stdlib-extensions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kmp-library-convention") 3 | id("publication-convention") 4 | } 5 | 6 | version = libs.versions.koTLVersion.get() 7 | -------------------------------------------------------------------------------- /libs/stdlib-extensions/src/commonMain/kotlin/kotl/stdlib/bytes/Int.kt: -------------------------------------------------------------------------------- 1 | package kotl.stdlib.bytes 2 | 3 | import kotlin.random.Random 4 | 5 | public fun Int.encodeToByteArray(): ByteArray = byteArrayOf( 6 | (this shr 0).toByte(), 7 | (this shr 8).toByte(), 8 | (this shr 16).toByte(), 9 | (this shr 24).toByte() 10 | ) 11 | 12 | public fun ByteArray.decodeInt(offset: Int): Int = 13 | (this[offset + 0].toInt() and 0xff shl 0) or 14 | (this[offset + 1].toInt() and 0xff shl 8) or 15 | (this[offset + 2].toInt() and 0xff shl 16) or 16 | (this[offset + 3].toInt() and 0xff shl 24) 17 | 18 | private fun main() { 19 | repeat(1_000_000_000) { 20 | val int = Random.nextInt() 21 | require(int.encodeToByteArray().decodeInt(offset = 0) == int) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libs/stdlib-extensions/src/commonMain/kotlin/kotl/stdlib/bytes/Long.kt: -------------------------------------------------------------------------------- 1 | package kotl.stdlib.bytes 2 | 3 | import kotlin.random.Random 4 | 5 | public fun Long.encodeToByteArray(): ByteArray = byteArrayOf( 6 | (this shr 0).toByte(), 7 | (this shr 8).toByte(), 8 | (this shr 16).toByte(), 9 | (this shr 24).toByte(), 10 | (this shr 32).toByte(), 11 | (this shr 40).toByte(), 12 | (this shr 48).toByte(), 13 | (this shr 56).toByte() 14 | ) 15 | 16 | public fun ByteArray.decodeLong(offset: Int): Long = 17 | (this[offset + 0].toLong() and 0xff shl 0) or 18 | (this[offset + 1].toLong() and 0xff shl 8) or 19 | (this[offset + 2].toLong() and 0xff shl 16) or 20 | (this[offset + 3].toLong() and 0xff shl 24) or 21 | (this[offset + 4].toLong() and 0xff shl 32) or 22 | (this[offset + 5].toLong() and 0xff shl 40) or 23 | (this[offset + 6].toLong() and 0xff shl 48) or 24 | (this[offset + 7].toLong() and 0xff shl 56) 25 | 26 | private fun main() { 27 | repeat(1_000_000_000) { 28 | val long = Random.nextLong() 29 | require(long.encodeToByteArray().decodeLong(offset = 0) == long) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /libs/stdlib-extensions/src/commonMain/kotlin/kotl/stdlib/bytes/Pad.kt: -------------------------------------------------------------------------------- 1 | package kotl.stdlib.bytes 2 | 3 | public fun ByteArray.padEnd( 4 | desiredLength: Int, 5 | padByte: Byte = 0 6 | ): ByteArray { 7 | val padSize = desiredLength - this.size 8 | return this + ByteArray(padSize) { padByte } 9 | } 10 | -------------------------------------------------------------------------------- /libs/stdlib-extensions/src/commonMain/kotlin/kotl/stdlib/int/NearestMultiple.kt: -------------------------------------------------------------------------------- 1 | package kotl.stdlib.int 2 | 3 | public fun Int.nearestMultipleOf(n: Int): Int { 4 | return if (this <= 0) 0 else ((this - 1) / n + 1) * n 5 | } 6 | -------------------------------------------------------------------------------- /libs/string-parser/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kmp-library-convention") 3 | id("publication-convention") 4 | } 5 | 6 | version = libs.versions.koTLVersion.get() 7 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Any.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public fun anyParser( 4 | vararg parsers: Parser 5 | ): Parser = parser { 6 | any(*parsers) 7 | } 8 | 9 | public fun anyParser( 10 | parsers: List> 11 | ): Parser = parser { 12 | any(parsers) 13 | } 14 | 15 | public fun ParserState.any( 16 | vararg parsers: Parser 17 | ): T = any(parsers.toList()) 18 | 19 | public fun ParserState.any( 20 | parsers: List> 21 | ): T { 22 | for (parser in parsers) { 23 | parser 24 | .tryParse() 25 | .onSuccess { return it } 26 | } 27 | fail() 28 | } 29 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Colon.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public fun ParserState.colon(): Char = consumeToken(':') 4 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Comma.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public fun ParserState.comma(): Char = consumeToken(',') 4 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Consume.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public typealias Consumer = Parser 4 | 5 | public fun ParserState.consume(string: String): String { 6 | if (remaining.startsWith(string)) { 7 | discard(n = string.length) 8 | return string 9 | } else { 10 | fail() 11 | } 12 | } 13 | 14 | public fun ParserState.consume(char: Char): Char { 15 | if (remaining.startsWith(char)) { 16 | discard(n = 1) 17 | return char 18 | } else { 19 | fail() 20 | } 21 | } 22 | 23 | public fun ParserState.consumeToken(string: String): String { 24 | whitespace() 25 | consume(string) 26 | whitespace() 27 | return string 28 | } 29 | 30 | public fun ParserState.consumeToken(char: Char): Char { 31 | whitespace() 32 | consume(char) 33 | whitespace() 34 | return char 35 | } 36 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Discard.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public fun ParserState.discard(n: Int) { 4 | try { 5 | remaining = remaining.substring(n) 6 | } catch (_: Throwable) { 7 | fail() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/LineParser.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public inline fun lineParser( 4 | crossinline block: ParserDSL 5 | ): Parser = parser { 6 | val value = block() 7 | semicolon() 8 | value 9 | } 10 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Many.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public fun manyParser( 4 | vararg parsers: Parser 5 | ): Parser> = parser { 6 | many(*parsers) 7 | } 8 | 9 | public fun ParserState.many( 10 | vararg parsers: Parser 11 | ): List = many { 12 | any(*parsers) 13 | } 14 | 15 | public fun ParserState.many( 16 | parser: ParserState.() -> T 17 | ): List = many(parser(parser)) 18 | 19 | public fun ParserState.many( 20 | parser: Parser 21 | ): List { 22 | val result = mutableListOf() 23 | 24 | while (true) { 25 | parser.tryParse() 26 | .onSuccess { value -> 27 | result += value 28 | } 29 | .onFailure { return result } 30 | } 31 | } 32 | 33 | public fun ParserState.many( 34 | parser: Parser, 35 | separator: Parser<*> 36 | ): List { 37 | val first = parser 38 | .tryParse() 39 | .getOrElse { return emptyList() } 40 | 41 | val parserWithSeparator = parser { 42 | separator.parse() 43 | parser.parse() 44 | } 45 | 46 | return listOf(first) + many(parserWithSeparator) 47 | } 48 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Map.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public inline fun Parser.map( 4 | crossinline transform: (T) -> R 5 | ): Parser = parser { 6 | transform(parse()) 7 | } 8 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Parser.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public fun interface Parser { 4 | public fun parse(string: String): ParserResult 5 | } 6 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/ParserDSL.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public typealias ParserDSL = ParserState.() -> T 4 | 5 | public class ParserState(public var remaining: String) { 6 | public fun Parser.parse(): T { 7 | return when (val result = parse(remaining)) { 8 | ParserResult.Failure -> fail() 9 | is ParserResult.Success -> { 10 | remaining = result.remaining 11 | result.value 12 | } 13 | } 14 | } 15 | 16 | public fun Parser.tryParse(): Result { 17 | return try { 18 | Result.success(parse()) 19 | } catch (failure: ParserFailure) { 20 | Result.failure(failure) 21 | } 22 | } 23 | } 24 | 25 | public class ParserFailure() : RuntimeException() 26 | 27 | public inline fun ParserState.fail(crossinline dsl: ParserDSL<*>) { 28 | fail(parser(dsl)) 29 | } 30 | 31 | /** 32 | * Fail if parser succeeded 33 | */ 34 | public fun ParserState.fail(parser: Parser<*>) { 35 | parser 36 | .tryParse() 37 | .onSuccess { fail() } 38 | } 39 | 40 | @Suppress("UnusedReceiverParameter") 41 | public fun ParserState.fail(): Nothing { 42 | throw ParserFailure() 43 | } 44 | 45 | public inline fun parser( 46 | crossinline block: ParserState.() -> T 47 | ): Parser { 48 | return Parser { tokens -> 49 | val context = ParserState(tokens) 50 | try { 51 | val result = context.block() 52 | ParserResult.Success(result, context.remaining) 53 | } catch (failure: ParserFailure) { 54 | ParserResult.Failure 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/ParserResult.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public sealed interface ParserResult { 4 | public data class Success( 5 | val value: T, 6 | val remaining: String 7 | ) : ParserResult 8 | public data object Failure : ParserResult 9 | } 10 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Safely.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public inline fun ParserState.safely( 4 | crossinline block: ParserState.() -> T 5 | ): T? = parser(block) 6 | .tryParse() 7 | .getOrNull() 8 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Semicolon.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public fun ParserState.semicolon(): Char = consumeToken(';') 4 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Take.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public inline fun ParserState.takeWhile(predicate: (Char) -> Boolean): String { 4 | val string = remaining.takeWhile(predicate) 5 | discard(string.length) 6 | return string 7 | } 8 | 9 | public fun ParserState.takeUntil(string: String): String { 10 | return many { 11 | fail { consume(string) } 12 | take(n = 1) 13 | }.joinToString(separator = "") 14 | } 15 | 16 | public fun ParserState.takeLine( 17 | includeNewline: Boolean = false 18 | ): String { 19 | val line = takeWhile { char -> char != '\n' } 20 | take(n = 1) 21 | return if (includeNewline) (line + '\n') else line 22 | } 23 | 24 | public fun ParserState.takeRegex(regex: Regex): String { 25 | val match = regex.find(remaining) ?: fail() 26 | if (match.range.first != 0) fail() 27 | val string = match.value 28 | discard(string.length) 29 | return string 30 | } 31 | 32 | public fun ParserState.take(n: Int): String { 33 | val string = remaining.take(n) 34 | discard(n) 35 | return string 36 | } 37 | 38 | public fun ParserState.takeInt(): Int = takeWhile { it.isDigit() } 39 | .toIntOrNull() 40 | ?: fail() 41 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/Whitespace.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser 2 | 3 | public fun ParserState.whitespace( 4 | newlines: Boolean = true 5 | ) { 6 | if (newlines) { 7 | takeWhile { char -> char.isWhitespace() } 8 | } else { 9 | takeWhile { char -> char.isWhitespace() && char != '\n' } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/string-parser/src/commonMain/kotlin/kotl/parser/debug/PrintDebugInfo.kt: -------------------------------------------------------------------------------- 1 | package kotl.parser.debug 2 | 3 | import kotl.parser.ParserState 4 | 5 | public fun ParserState.printDebugInfo(tag: String? = null) { 6 | println(""" 7 | PARSER STATE${if (tag == null) "" else " ($tag)"}: 8 | remaining: 9 | """.trimIndent() + remaining + '\n') 10 | } 11 | -------------------------------------------------------------------------------- /schema/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kmp-library-convention") 3 | id("publication-convention") 4 | } 5 | 6 | version = libs.versions.koTLVersion.get() 7 | 8 | dependencies { 9 | commonMainImplementation(projects.libs.stringParser) 10 | } 11 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/element/TLSchemaCallable.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.element 2 | 3 | import kotl.schema.types.* 4 | 5 | public data class TLSchemaCallable( 6 | val name: FunctionName, 7 | val hash: FunctionHash?, 8 | val typeParameters: List, 9 | val parameters: List, 10 | val returnType: TypeReference 11 | ) : TLSchemaElement 12 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/element/TLSchemaComment.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.element 2 | 3 | public data class TLSchemaComment(val string: String) : TLSchemaElement 4 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/element/TLSchemaElement.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.element 2 | 3 | public sealed interface TLSchemaElement 4 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/element/TLSchemaSectionDivider.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.element 2 | 3 | public sealed interface TLSchemaSectionDivider : TLSchemaElement { 4 | public data object Functions : TLSchemaSectionDivider { 5 | override fun toString(): String = "TLSchemaSectionDivider.Functions" 6 | } 7 | public data object Types : TLSchemaSectionDivider { 8 | override fun toString(): String = "TLSchemaSectionDivider.Types" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/parser/SchemaParser.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.parser 2 | 3 | import kotl.parser.* 4 | import kotl.schema.element.TLSchemaElement 5 | import kotl.schema.parser.comment.commentParser 6 | import kotl.schema.parser.function.functionParser 7 | import kotl.schema.parser.line.emptyLineParser 8 | import kotl.schema.parser.section.sectionDividerParser 9 | 10 | internal fun schemaParser(): Parser> { 11 | return parser { 12 | many( 13 | functionParser(), 14 | sectionDividerParser(), 15 | commentParser(), 16 | emptyLineParser() 17 | ).filterNotNull() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/parser/comment/CommentParser.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.parser.comment 2 | 3 | import kotl.parser.Parser 4 | import kotl.parser.anyParser 5 | import kotl.schema.element.TLSchemaComment 6 | import kotl.schema.parser.comment.multiline.multilineCommentParser 7 | import kotl.schema.parser.comment.singleline.singleLineCommentParser 8 | 9 | internal fun commentParser(): Parser { 10 | return anyParser( 11 | singleLineCommentParser(), 12 | multilineCommentParser() 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/parser/comment/multiline/MultilineCommentParser.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.parser.comment.multiline 2 | 3 | import kotl.parser.* 4 | import kotl.schema.element.TLSchemaComment 5 | 6 | internal fun multilineCommentParser(): Parser = parser { 7 | val message = buildString { 8 | append(commentStart()) 9 | val body = many( 10 | multilineCommentParser().map { it.string }, 11 | parser { 12 | fail { commentEnd() } 13 | take(n = 1) 14 | } 15 | ).joinToString(separator = "") 16 | append(body) 17 | append(commentEnd()) 18 | } 19 | 20 | TLSchemaComment(message) 21 | } 22 | 23 | private fun ParserState.commentStart(): String = consume("/*") 24 | private fun ParserState.commentEnd(): String = consume("*/") 25 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/parser/comment/singleline/SingleLineParser.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.parser.comment.singleline 2 | 3 | import kotl.parser.* 4 | import kotl.schema.element.TLSchemaComment 5 | 6 | internal fun singleLineCommentParser() = parser { 7 | val string = commentStart() + takeLine() 8 | TLSchemaComment(string) 9 | } 10 | 11 | private fun ParserState.commentStart() = consume(string = "//") 12 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/parser/function/FunctionParser.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.parser.function 2 | 3 | import kotl.parser.* 4 | import kotl.parser.debug.printDebugInfo 5 | import kotl.schema.element.TLSchemaCallable 6 | import kotl.schema.parser.type.type 7 | import kotl.schema.types.* 8 | import kotl.schema.types.ParameterName 9 | 10 | internal fun functionParser(): Parser = lineParser { 11 | val name = name() 12 | val hash = hash() 13 | val typeParameters = typeParameters() 14 | val parameters = parameters() 15 | safely { consumeToken('?') } 16 | consumeToken('=') 17 | val returnType = type() 18 | 19 | TLSchemaCallable( 20 | name = name, 21 | hash = hash, 22 | typeParameters = typeParameters, 23 | parameters = parameters, 24 | returnType = returnType 25 | ) 26 | } 27 | 28 | private fun ParserState.name(): FunctionName { 29 | val declaration = takeRegex(FunctionName.Regex) 30 | return FunctionName(declaration) 31 | } 32 | 33 | private fun ParserState.hash(): FunctionHash? { 34 | val hash = safely { consume('#') } ?: return null 35 | val string = hash + takeWhile { char -> !char.isWhitespace() } 36 | whitespace() 37 | return FunctionHash(string) 38 | } 39 | 40 | private fun ParserState.typeParameters(): List { 41 | safely { consumeToken('{') } ?: return emptyList() 42 | 43 | val parameters = many( 44 | parser = parser { 45 | val name = parameterName() 46 | consumeToken(':') 47 | val upperBound = type() 48 | TypeParameter(name, upperBound) 49 | }, 50 | separator = parser { comma() } 51 | ) 52 | 53 | consumeToken('}') 54 | return parameters 55 | } 56 | 57 | private fun ParserState.parameters(): List { 58 | return many( 59 | parser = parser { 60 | val name = parameterName() 61 | colon() 62 | val nullabilityMarker = nullabilityMarker() 63 | val type = type() 64 | Parameter(name, type, nullabilityMarker) 65 | }, 66 | separator = parser { whitespace() } 67 | ) 68 | } 69 | 70 | private fun ParserState.parameterName(): ParameterName { 71 | val declaration = takeRegex(ParameterName.Regex) 72 | return ParameterName(declaration) 73 | } 74 | 75 | private fun ParserState.nullabilityMarker(): NullabilityMarker? { 76 | return safely { 77 | val reference = parameterName() 78 | consume('.') 79 | val shift = takeInt() 80 | consume('?') 81 | NullabilityMarker(reference, shift) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/parser/line/EmptyLineParser.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.parser.line 2 | 3 | import kotl.parser.Parser 4 | import kotl.parser.fail 5 | import kotl.parser.parser 6 | import kotl.parser.takeLine 7 | 8 | internal fun emptyLineParser(): Parser = parser { 9 | val line = takeLine() 10 | if(line.isNotBlank()) fail() 11 | null 12 | } 13 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/parser/section/SectionDividerParser.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.parser.section 2 | 3 | import kotl.parser.* 4 | import kotl.schema.element.TLSchemaSectionDivider 5 | 6 | internal fun sectionDividerParser(): Parser = 7 | parser { 8 | consume("---") 9 | val sectionName = takeWhile { it != '-' } 10 | consume("---") 11 | 12 | when (sectionName) { 13 | "functions" -> TLSchemaSectionDivider.Functions 14 | "types" -> TLSchemaSectionDivider.Types 15 | else -> fail() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/parser/type/TypeParser.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.parser.type 2 | 3 | import kotl.parser.* 4 | import kotl.schema.parser.line.emptyLineParser 5 | import kotl.schema.types.TypeArgument 6 | import kotl.schema.types.TypeReference 7 | import kotl.schema.types.TypeName 8 | 9 | internal fun ParserState.type(): TypeReference { 10 | exclamationMark() 11 | val name = name() 12 | val arguments = arguments() 13 | return TypeReference(name, arguments) 14 | } 15 | 16 | private fun ParserState.exclamationMark() { 17 | safely { consumeToken('!') } 18 | } 19 | 20 | private fun ParserState.name(): TypeName { 21 | val string = takeRegex(TypeName.Regex) 22 | return TypeName(string) 23 | } 24 | 25 | private fun ParserState.arguments(): List { 26 | return safely { 27 | consumeToken('<') 28 | val arguments = many( 29 | parser = parser { type() }, 30 | separator = parser { comma() } 31 | ).map { reference -> TypeArgument(reference) } 32 | consumeToken('>') 33 | arguments 34 | }.orEmpty() 35 | } 36 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/prettyPrint/PrettyPrint.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.prettyPrint 2 | 3 | import kotl.schema.element.TLSchemaComment 4 | import kotl.schema.element.TLSchemaElement 5 | import kotl.schema.element.TLSchemaCallable 6 | import kotl.schema.element.TLSchemaSectionDivider 7 | import kotl.schema.types.TypeReference 8 | 9 | public fun TLSchemaElement.prettyString(): String = when (this) { 10 | is TLSchemaComment -> this.string 11 | is TLSchemaCallable -> buildString { 12 | append(name.string) 13 | if (hash != null) append(hash.string) 14 | append(' ') 15 | if (typeParameters.isNotEmpty()) { 16 | append('{') 17 | for (parameter in typeParameters) { 18 | append(parameter.name.string) 19 | append(':') 20 | append(parameter.upperBound.prettyString()) 21 | } 22 | append('}') 23 | } 24 | append(' ') 25 | for (parameter in parameters) { 26 | append(parameter.name.string) 27 | append(':') 28 | append(parameter.type.prettyString()) 29 | append(' ') 30 | } 31 | append("= ") 32 | append(returnType.prettyString()) 33 | append(';') 34 | } 35 | is TLSchemaSectionDivider.Functions -> "---functions---" 36 | is TLSchemaSectionDivider.Types -> "---types---" 37 | } 38 | 39 | public fun TLSchemaElement.prettyPrint(): Unit = println(prettyString()) 40 | 41 | public fun TypeReference.prettyString(): String = buildString { 42 | append(name.string) 43 | if (typeArguments.isNotEmpty()) { 44 | append('<') 45 | for (argument in typeArguments) { 46 | append(argument.reference.prettyString()) 47 | } 48 | append('>') 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/types/FunctionHash.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.types 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class FunctionHash(public val string: String) { 7 | init { 8 | require(string.matches(Regex)) { "Function hash should be hash mark ( # ) and exactly 8 hexadecimal digits" } 9 | } 10 | 11 | public companion object { 12 | public val Regex: Regex = Regex("#[a-f0-9]+") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/types/FunctionName.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.types 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class FunctionName( 7 | public val string: String 8 | ) { 9 | public val namespace: String get() = string.substringBefore('.') 10 | public val simpleName: String get() = string.substringAfter('.') 11 | 12 | init { 13 | require(string.matches(Regex)) { "Declaration name should be an alphanumeric string" } 14 | } 15 | 16 | public companion object { 17 | public val Regex: Regex = Regex("[a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z0-9_]+)*") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/types/NullabilityMarker.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.types 2 | 3 | public data class NullabilityMarker( 4 | val reference: ParameterName, 5 | val shift: Int 6 | ) 7 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/types/Parameter.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.types 2 | 3 | public data class Parameter( 4 | val name: ParameterName, 5 | val type: TypeReference, 6 | val nullabilityMarker: NullabilityMarker? 7 | ) 8 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/types/ParameterName.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.types 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class ParameterName( 7 | public val string: String 8 | ) { 9 | init { 10 | require(string.matches(Regex)) { "Declaration name should be an alphanumeric string" } 11 | } 12 | 13 | public companion object { 14 | public val Regex: Regex = Regex("[a-zA-Z][a-zA-Z0-9_]*") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/types/TypeArgument.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.types 2 | 3 | public data class TypeArgument( 4 | val reference: TypeReference 5 | ) 6 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/types/TypeName.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.types 2 | 3 | import kotlin.jvm.JvmInline 4 | 5 | @JvmInline 6 | public value class TypeName( 7 | public val string: String 8 | ) { 9 | public val namespace: String get() = string.substringBefore('.') 10 | public val simpleName: String get() = string.substringAfter('.') 11 | 12 | init { 13 | require(string.matches(Regex)) { "Declaration name should be an alphanumeric string" } 14 | } 15 | 16 | public companion object { 17 | public val Regex: Regex = Regex("[a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z0-9_]+)*|#") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/types/TypeParameter.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.types 2 | 3 | public data class TypeParameter( 4 | val name: ParameterName, 5 | val upperBound: TypeReference 6 | ) 7 | -------------------------------------------------------------------------------- /schema/src/commonMain/kotlin/kotl/schema/types/TypeReference.kt: -------------------------------------------------------------------------------- 1 | package kotl.schema.types 2 | 3 | public data class TypeReference( 4 | val name: TypeName, 5 | val typeArguments: List 6 | ) 7 | -------------------------------------------------------------------------------- /schema/src/jvmMain/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | import kotl.parser.ParserResult 2 | import kotl.schema.element.TLSchemaCallable 3 | import kotl.schema.parser.schemaParser 4 | import kotl.schema.types.Parameter 5 | import kotl.schema.types.TypeName 6 | import kotl.schema.types.TypeParameter 7 | import kotl.schema.types.TypeReference 8 | import java.io.File 9 | 10 | private fun main() { 11 | val parser = schemaParser() 12 | val file = File(System.getenv("user.dir"), "schema158.tl").apply { 13 | createNewFile() 14 | } 15 | 16 | val result = parser.parse(file.readText()) as ParserResult.Success 17 | 18 | for (element in result.value.filterIsInstance()) { 19 | println(template(element)) 20 | println() 21 | println() 22 | } 23 | } 24 | 25 | private fun template( 26 | function: TLSchemaCallable 27 | ) = """ 28 | @Serializable 29 | @TLRpcCall(crc32 = 0x${function.hash?.string?.drop(n = 1)}_u) 30 | data class ${function.name.string}${typeParamsTemplate(function.typeParameters)}(${paramsTemplate(function.parameters)}) 31 | """.trimIndent() 32 | 33 | private fun typeParamsTemplate( 34 | parameters: List 35 | ) = buildString { 36 | if (parameters.isEmpty()) return "" 37 | append('<') 38 | for ((i, parameter) in parameters.withIndex()) { 39 | append(parameter.name.string) 40 | append(" : ") 41 | append(typeTemplate(parameter.upperBound)) 42 | if (i != parameters.lastIndex) append(", ") 43 | } 44 | append('>') 45 | } 46 | 47 | private fun paramsTemplate(parameters: List): String = buildString { 48 | if (parameters.isEmpty()) return "" 49 | appendLine() 50 | append( 51 | buildString { 52 | for ((i, parameter) in parameters.withIndex()) { 53 | append("val ${parameter.name.string}: ${typeTemplate(parameter.type)}") 54 | if (i != parameters.lastIndex) appendLine(',') 55 | } 56 | }.prependIndent() 57 | ) 58 | appendLine() 59 | } 60 | 61 | private fun typeTemplate(type: TypeReference): String = buildString { 62 | append(type.name.kotlinString) 63 | if (type.typeArguments.isNotEmpty()) { 64 | append('<') 65 | for ((i, argument) in type.typeArguments.withIndex()) { 66 | append(typeTemplate(argument.reference)) 67 | if (i != type.typeArguments.lastIndex) append(", ") 68 | } 69 | append('>') 70 | } 71 | } 72 | 73 | private val TypeName.kotlinString: String get() = when (string) { 74 | "Type" -> "Any" 75 | "#" -> "Int" 76 | else -> string 77 | } 78 | -------------------------------------------------------------------------------- /serialization/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kmp-library-convention") 3 | id("publication-convention") 4 | } 5 | 6 | version = libs.versions.koTLVersion.get() 7 | 8 | dependencies { 9 | commonMainApi(projects.core) 10 | commonMainImplementation(projects.libs.stdlibExtensions) 11 | commonMainImplementation(libs.kotlinxSerialization) 12 | } 13 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/TL.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization 2 | 3 | import kotl.core.decoder.decodeFromByteArray 4 | import kotl.core.descriptor.TLExpressionDescriptor 5 | import kotl.core.element.TLElement 6 | import kotl.core.element.TLExpression 7 | import kotl.core.encoder.encodeToByteArray 8 | import kotl.serialization.decoder.SingleElementReader 9 | import kotl.serialization.decoder.TLDecoder 10 | import kotl.serialization.encoder.SingleElementWriter 11 | import kotl.serialization.encoder.TLEncoder 12 | import kotl.serialization.extensions.asTLDescriptor 13 | import kotlinx.serialization.BinaryFormat 14 | import kotlinx.serialization.DeserializationStrategy 15 | import kotlinx.serialization.SerializationStrategy 16 | import kotlinx.serialization.modules.EmptySerializersModule 17 | import kotlinx.serialization.modules.SerializersModule 18 | import kotlinx.serialization.serializer 19 | 20 | /** 21 | * @param supportOptionalHash whether to enable hash calculation 22 | * when no hash present or fail 23 | */ 24 | public sealed class TL( 25 | public val supportOptionalHash: Boolean, 26 | override val serializersModule: SerializersModule 27 | ) : BinaryFormat { 28 | 29 | public fun decodeFromTLElement( 30 | deserializer: DeserializationStrategy, 31 | element: TLElement 32 | ): T { 33 | val reader = SingleElementReader(element) 34 | val decoder = TLDecoder(this, reader) 35 | return decoder.decodeSerializableValue(deserializer) 36 | } 37 | 38 | override fun decodeFromByteArray( 39 | deserializer: DeserializationStrategy, 40 | bytes: ByteArray 41 | ): T = decodeFromTLElement( 42 | deserializer = deserializer, 43 | element = parseToTLElement( 44 | descriptor = deserializer.descriptor.asTLDescriptor(), 45 | bytes = bytes 46 | ) 47 | ) 48 | 49 | public fun parseToTLElement( 50 | descriptor: TLExpressionDescriptor, 51 | bytes: ByteArray 52 | ): TLExpression = descriptor.decodeFromByteArray(bytes) 53 | 54 | public fun encodeToTLElement( 55 | serializer: SerializationStrategy, 56 | value: T 57 | ): TLElement { 58 | val writer = SingleElementWriter() 59 | val encoder = TLEncoder(this, writer) 60 | encoder.encodeSerializableValue(serializer, value) 61 | return writer.encoded 62 | } 63 | 64 | override fun encodeToByteArray( 65 | serializer: SerializationStrategy, 66 | value: T 67 | ): ByteArray = encodeToTLElement(serializer, value) 68 | .encodeToByteArray() 69 | 70 | public companion object Default : TL( 71 | supportOptionalHash = true, 72 | EmptySerializersModule() 73 | ) 74 | } 75 | 76 | public inline fun TL.encodeToTLElement(value: T): TLElement = 77 | encodeToTLElement(serializer(), value) 78 | 79 | public inline fun TL.decodeFromTLElement(element: TLElement): T = 80 | decodeFromTLElement(serializer(), element) 81 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/annotation/Crc32.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.annotation 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.SerialInfo 5 | 6 | @OptIn(ExperimentalSerializationApi::class) 7 | @SerialInfo 8 | @Target(AnnotationTarget.CLASS) 9 | public annotation class Crc32(val value: UInt) 10 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/annotation/TLBare.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.annotation 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.SerialInfo 5 | 6 | /** 7 | * Annotate parameters that you want to be encoded as bare types 8 | * https://core.telegram.org/mtproto/serialize#boxed-and-bare-types 9 | * 10 | * You can use Bare as an alternative 11 | */ 12 | @OptIn(ExperimentalSerializationApi::class) 13 | @SerialInfo 14 | @Target(AnnotationTarget.PROPERTY) 15 | public annotation class TLBare 16 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/annotation/TLRpc.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.annotation 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.SerialInfo 5 | 6 | @OptIn(ExperimentalSerializationApi::class) 7 | @SerialInfo 8 | @Target(AnnotationTarget.CLASS) 9 | public annotation class TLRpc(val crc32: UInt) 10 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/annotation/TLSize.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.annotation 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.SerialInfo 5 | 6 | @OptIn(ExperimentalSerializationApi::class) 7 | @SerialInfo 8 | @Target(AnnotationTarget.PROPERTY) 9 | public annotation class TLSize(val bits: Int) 10 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/bare/Bare.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.bare 2 | 3 | import kotl.serialization.annotation.TLBare 4 | import kotlinx.serialization.Serializable 5 | import kotlin.jvm.JvmInline 6 | 7 | /** 8 | * Use with parameters that you want to be encoded as bare types 9 | * https://core.telegram.org/mtproto/serialize#boxed-and-bare-types 10 | * 11 | * You can use @TLBare as an alternative 12 | */ 13 | @Serializable 14 | @JvmInline 15 | public value class Bare( 16 | @TLBare 17 | public val value: T 18 | ) 19 | 20 | public val T.bare: Bare get() = Bare(value = this) 21 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/bytes/Bytes.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.bytes 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.KSerializer 5 | import kotlinx.serialization.Serializable 6 | import kotlinx.serialization.builtins.ByteArraySerializer 7 | import kotlinx.serialization.descriptors.SerialDescriptor 8 | import kotlinx.serialization.encoding.Decoder 9 | import kotlinx.serialization.encoding.Encoder 10 | 11 | @Serializable(with = Bytes.Serializer::class) 12 | public data class Bytes(public val payload: ByteArray) { 13 | override fun equals(other: Any?): Boolean { 14 | if (this === other) return true 15 | if (other !is Bytes) return false 16 | return payload.contentEquals(other.payload) 17 | } 18 | 19 | override fun hashCode(): Int { 20 | return payload.contentHashCode() 21 | } 22 | 23 | @OptIn(ExperimentalSerializationApi::class) 24 | public object Serializer : KSerializer { 25 | override val descriptor: SerialDescriptor = SerialDescriptor( 26 | serialName = "kotl.serialization.bytes.Bytes", 27 | original = ByteArraySerializer().descriptor 28 | ) 29 | 30 | override fun deserialize(decoder: Decoder): Bytes { 31 | val payload = decoder.decodeSerializableValue(ByteArraySerializer()) 32 | return Bytes(payload) 33 | } 34 | 35 | override fun serialize(encoder: Encoder, value: Bytes) { 36 | encoder.encodeSerializableValue(ByteArraySerializer(), value.payload) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/decoder/TLDecoder.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.decoder 2 | 3 | import kotl.core.element.* 4 | import kotl.serialization.TL 5 | import kotl.serialization.annotation.TLBare 6 | import kotl.serialization.annotation.TLSize 7 | import kotl.serialization.extensions.crc32 8 | import kotl.serialization.extensions.tlRpc 9 | import kotlinx.serialization.ExperimentalSerializationApi 10 | import kotlinx.serialization.SerializationException 11 | import kotlinx.serialization.descriptors.* 12 | import kotlinx.serialization.encoding.AbstractDecoder 13 | import kotlinx.serialization.encoding.CompositeDecoder 14 | import kotlinx.serialization.encoding.Decoder 15 | import kotlinx.serialization.modules.SerializersModule 16 | 17 | @OptIn(ExperimentalSerializationApi::class) 18 | internal class TLDecoder( 19 | private val tl: TL, 20 | private val reader: TLElementReader, 21 | private val parentDescriptor: SerialDescriptor? = null, 22 | private var nextElementIndex: Int = 0 23 | ) : AbstractDecoder() { 24 | override fun decodeElementIndex(descriptor: SerialDescriptor): Int { 25 | if (reader.isDone()) return CompositeDecoder.DECODE_DONE 26 | return nextElementIndex++ 27 | } 28 | 29 | override fun decodeBoolean(): Boolean { 30 | val element = reader.nextElement() 31 | element as? TLBoolean 32 | ?: throw SerializationException("TLBoolean expected, but $element found") 33 | return element.boolean 34 | } 35 | 36 | override fun decodeInt(): Int { 37 | val element = reader.nextElement() 38 | element as? Int 39 | ?: throw SerializationException("Int expected, but $element found") 40 | return element 41 | } 42 | 43 | override fun decodeLong(): Long { 44 | val element = reader.nextElement() 45 | element as? TLInt64 46 | ?: throw SerializationException("TLInt64 expected, but $element found") 47 | return element.long 48 | } 49 | 50 | override fun decodeDouble(): Double { 51 | val element = reader.nextElement() 52 | element as? TLDouble 53 | ?: throw SerializationException("TLDouble expected, but $element found") 54 | return element.double 55 | } 56 | 57 | override fun decodeString(): String { 58 | val element = reader.nextElement() 59 | element as? TLString 60 | ?: throw SerializationException("TLString expected, but $element found") 61 | return element.string 62 | } 63 | 64 | override fun decodeByte(): Byte { 65 | if (reader !is BytesElementReader) unsupportedPrimitive("Byte") 66 | return reader.nextElement() 67 | } 68 | 69 | override fun decodeNull(): Nothing? { 70 | val element = reader.nextElement() 71 | element as? TLNull 72 | ?: throw SerializationException("TLNull expected, but $element found") 73 | return null 74 | } 75 | 76 | override fun decodeInline(descriptor: SerialDescriptor): Decoder { 77 | return TLDecoder( 78 | tl = tl, 79 | reader = reader, 80 | parentDescriptor = descriptor, 81 | nextElementIndex = 1 82 | ) 83 | } 84 | 85 | override fun beginStructure( 86 | descriptor: SerialDescriptor 87 | ): CompositeDecoder { 88 | if (descriptor.kind == PolymorphicKind.SEALED) { 89 | val constructor = reader.nextElement() 90 | constructor as? TLConstructor ?: throw SerializationException("TLConstructor expected, but $constructor got") 91 | constructor as? TLConstructor.Boxed ?: throw SerializationException("Sealed structured may be only decoded when they are not bare") 92 | 93 | val crc32 = constructor.crc32 94 | val constructorDescriptor = descriptor.getElementDescriptor(index = 1).elementDescriptors 95 | .firstOrNull { child -> child.crc32?.value == crc32 } 96 | 97 | val className = constructorDescriptor?.serialName 98 | 99 | if (className == null) { 100 | val constructors = descriptor.getElementDescriptor(index = 1) 101 | .elementDescriptors 102 | .mapNotNull { child -> child.crc32?.value } 103 | error("Cannot find constructor with id ${crc32.toString(radix = 16)}. Available constructors: ${constructors.map { it.toString(radix = 16) }}") 104 | } 105 | 106 | return TLDecoder(tl, SealedElementReader(className, constructor)) 107 | } 108 | 109 | return when (descriptor.kind as? StructureKind) { 110 | StructureKind.LIST -> { 111 | val vector = reader.nextElement() as TLElement 112 | 113 | val intDecoder = intDecoder(vector) 114 | if (intDecoder != null) return intDecoder 115 | 116 | val bytesDecoder = bytesDecoder(vector) 117 | if (bytesDecoder != null) return bytesDecoder 118 | 119 | vector as? TLVector ?: throw SerializationException("TLVector expected, but $vector got") 120 | TLDecoder(tl, ListElementReader(vector)) 121 | } 122 | StructureKind.CLASS, StructureKind.OBJECT -> { 123 | val crc32 = descriptor.crc32 124 | val rpcCall = descriptor.tlRpc 125 | 126 | val constructor = reader.nextElement() 127 | constructor as? TLConstructor ?: throw SerializationException("TLConstructor expected, but $constructor got") 128 | 129 | val index = nextElementIndex - 1 130 | val bare = parentDescriptor 131 | ?.getElementAnnotations(index) 132 | ?.filterIsInstance() 133 | ?.firstOrNull() 134 | 135 | when { 136 | bare != null -> TLDecoder(tl, ConstructorElementReader(constructor), descriptor) 137 | crc32 != null -> TLDecoder(tl, ConstructorElementReader(constructor), descriptor) 138 | rpcCall != null -> throw SerializationException("@TLRpcCall does not intended to be deserialized") 139 | else -> throw SerializationException("Your class ${descriptor.serialName} should be annotated with @Crc32 for constructors or @TLRpcCall for functions to make it compatible with TL") 140 | } 141 | } 142 | else -> error("Unsupported structure kind ${descriptor.kind}") 143 | } 144 | } 145 | 146 | private fun intDecoder(int: TLElement): TLDecoder? { 147 | if (int !is TLInt) return null 148 | 149 | val index = nextElementIndex - 1 150 | val size = parentDescriptor 151 | ?.getElementAnnotations(index) 152 | ?.filterIsInstance() 153 | ?.firstOrNull() 154 | 155 | require(size != null && size.bits == (int.data.size * Int.SIZE_BITS)) { 156 | "descriptor.size: ${size?.bits}, vector.size: ${int.data.size}" 157 | } 158 | return TLDecoder(tl, IntElementReader(int)) 159 | } 160 | 161 | private fun bytesDecoder(bytes: TLElement): TLDecoder? { 162 | if (bytes !is TLBytes) return null 163 | return TLDecoder(tl, BytesElementReader(bytes)) 164 | } 165 | 166 | override fun decodeChar(): Char = unsupportedPrimitive("Char") 167 | override fun decodeFloat(): Float = unsupportedPrimitive("Float") 168 | override fun decodeShort(): Short = unsupportedPrimitive("Short") 169 | override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = error("TL doesn't support enums") 170 | 171 | override val serializersModule: SerializersModule 172 | get() = tl.serializersModule 173 | 174 | private fun unsupportedPrimitive( 175 | name: String 176 | ): Nothing = throw SerializationException("TL doesn't support $name. All supported primitives are: Boolean, Int, Long, String, Double") 177 | } 178 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/decoder/TLElementReader.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.decoder 2 | 3 | import kotl.core.element.* 4 | 5 | internal interface TLElementReader { 6 | fun isDone(): Boolean 7 | fun nextElement(): Any? 8 | } 9 | 10 | internal class SingleElementReader(private val value: TLElement) : TLElementReader { 11 | private var isDone = false 12 | override fun isDone(): Boolean = isDone 13 | override fun nextElement(): TLElement { 14 | if (isDone()) throw NoSuchElementException() 15 | isDone = true 16 | return value 17 | } 18 | } 19 | 20 | internal class BytesElementReader( 21 | value: TLBytes 22 | ) : TLElementReader { 23 | private val iterator = value.bytes.iterator() 24 | override fun isDone(): Boolean = !iterator.hasNext() 25 | override fun nextElement(): Byte = iterator.nextByte() 26 | } 27 | 28 | // big integers interpreted as int arrays 29 | internal class IntElementReader( 30 | value: TLInt 31 | ) : TLElementReader { 32 | private val iterator = value.data.iterator() 33 | 34 | override fun isDone(): Boolean = !iterator.hasNext() 35 | override fun nextElement(): Int = iterator.next() 36 | } 37 | 38 | internal class ListElementReader( 39 | value: TLVector 40 | ) : TLElementReader { 41 | private val iterator = value.elements.iterator() 42 | 43 | override fun isDone(): Boolean = !iterator.hasNext() 44 | override fun nextElement(): TLElement = iterator.next() 45 | } 46 | 47 | internal class ConstructorElementReader( 48 | value: TLConstructor 49 | ) : TLElementReader { 50 | private val iterator = value.parameters.iterator() 51 | 52 | override fun isDone(): Boolean = !iterator.hasNext() 53 | override fun nextElement(): TLElement = iterator.next() 54 | } 55 | 56 | internal class SealedElementReader( 57 | className: String, 58 | constructor: TLConstructor 59 | ) : TLElementReader { 60 | private val iterator = listOf(className.typedLanguage, constructor).iterator() 61 | 62 | override fun isDone(): Boolean = !iterator.hasNext() 63 | override fun nextElement(): TLElement = iterator.next() 64 | } 65 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/encoder/TLElementWriter.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.encoder 2 | 3 | import kotl.core.descriptor.TLIntDescriptor 4 | import kotl.core.element.* 5 | 6 | internal interface TLElementWriter { 7 | fun writeElement(element: Any?) 8 | 9 | fun endStructure() {} 10 | } 11 | 12 | internal class SingleElementWriter : TLElementWriter { 13 | lateinit var encoded: TLElement 14 | 15 | override fun writeElement(element: Any?) { 16 | require(element is TLElement) { "Only TLElement supported for this writer, got $element" } 17 | encoded = element 18 | } 19 | } 20 | 21 | internal class BytesElementWriter( 22 | private val parent: TLElementWriter 23 | ) : TLElementWriter { 24 | private val bytes = mutableListOf() 25 | 26 | override fun writeElement(element: Any?) { 27 | require(element is Byte) { "Only kotlin.Byte supported for this writer, got $element" } 28 | bytes += element 29 | } 30 | 31 | override fun endStructure() { 32 | val element = TLBytes(bytes.toByteArray()) 33 | parent.writeElement(element) 34 | } 35 | } 36 | 37 | internal class IntElementWriter( 38 | private val parent: TLElementWriter, 39 | private val descriptor: TLIntDescriptor 40 | ) : TLElementWriter { 41 | private val ints = IntArray( 42 | size = descriptor.sizeBytes / Int.SIZE_BYTES 43 | ) 44 | private var index = 0 45 | 46 | override fun writeElement(element: Any?) { 47 | require(element is Int) { "Only kotlin.Int supported for this writer, got $element" } 48 | ints[index++] = element 49 | } 50 | 51 | override fun endStructure() { 52 | val element = descriptor.create(ints) 53 | parent.writeElement(element) 54 | } 55 | } 56 | 57 | internal class ListElementWriter( 58 | private val parent: TLElementWriter 59 | ) : TLElementWriter { 60 | private var encoded = TLVector.Empty 61 | 62 | override fun writeElement(element: Any?) { 63 | require(element is TLExpression) { "Cannot write $element to vector, only expressions are allowed" } 64 | encoded = encoded.copy(elements = encoded.elements + element) 65 | } 66 | 67 | override fun endStructure() = parent.writeElement(encoded) 68 | } 69 | 70 | internal class ConstructorElementWriter( 71 | crc32: UInt?, 72 | private val parent: TLElementWriter 73 | ) : TLElementWriter { 74 | private var encoded = TLConstructor(crc32, emptyList()) 75 | 76 | override fun writeElement(element: Any?) { 77 | require(element is TLExpression) { "Cannot write $element to constructor, only expressions are allowed" } 78 | encoded = encoded.copy(parameters = encoded.parameters + element) 79 | } 80 | 81 | override fun endStructure() = parent.writeElement(encoded) 82 | } 83 | 84 | private fun TLConstructor.copy(parameters: List): TLConstructor = 85 | when (this) { 86 | is TLConstructor.Bare -> copy(parameters = parameters) 87 | is TLConstructor.Boxed -> copy(parameters = parameters) 88 | } 89 | 90 | internal class FunctionElementWriter( 91 | crc32: UInt, 92 | private val parent: TLElementWriter 93 | ) : TLElementWriter { 94 | private var encoded = TLFunction(crc32, emptyList()) 95 | 96 | override fun writeElement(element: Any?) { 97 | require(element is TLExpression) { "Cannot write $element to function, only expressions are allowed" } 98 | encoded = encoded.copy(parameters = encoded.parameters + element) 99 | } 100 | 101 | override fun endStructure() = parent.writeElement(encoded) 102 | } 103 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/encoder/TLEncoder.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.encoder 2 | 3 | import kotl.core.descriptor.TLIntDescriptor 4 | import kotl.core.element.TLExpression 5 | import kotl.core.element.TLInt32 6 | import kotl.core.element.typedLanguage 7 | import kotl.serialization.TL 8 | import kotl.serialization.annotation.TLBare 9 | import kotl.serialization.annotation.TLSize 10 | import kotl.serialization.extensions.crc32 11 | import kotl.serialization.extensions.tlRpc 12 | import kotlinx.serialization.ExperimentalSerializationApi 13 | import kotlinx.serialization.SerializationException 14 | import kotlinx.serialization.SerializationStrategy 15 | import kotlinx.serialization.descriptors.* 16 | import kotlinx.serialization.encoding.AbstractEncoder 17 | import kotlinx.serialization.encoding.CompositeEncoder 18 | import kotlinx.serialization.encoding.Encoder 19 | 20 | @OptIn(ExperimentalSerializationApi::class) 21 | internal class TLEncoder( 22 | private val tl: TL, 23 | private val writer: TLElementWriter, 24 | private val parentDescriptor: SerialDescriptor? = null 25 | ): AbstractEncoder() { 26 | private var elementIndex: Int = 0 27 | 28 | override fun encodeSerializableValue( 29 | serializer: SerializationStrategy, 30 | value: T 31 | ) { 32 | checkDescriptor(serializer.descriptor) 33 | super.encodeSerializableValue(serializer, value) 34 | } 35 | 36 | private val supportedDescriptors = listOf( 37 | PrimitiveKind.INT, 38 | PrimitiveKind.LONG, 39 | PrimitiveKind.DOUBLE, 40 | PrimitiveKind.STRING, 41 | StructureKind.CLASS, 42 | StructureKind.OBJECT, 43 | StructureKind.LIST, 44 | PolymorphicKind.SEALED 45 | ) 46 | 47 | private fun checkDescriptor(descriptor: SerialDescriptor) { 48 | require(descriptor.kind in supportedDescriptors) { "TL doesn't support ${descriptor.kind}" } 49 | } 50 | 51 | override fun encodeInt(value: Int) { 52 | if (writer is IntElementWriter) { 53 | writer.writeElement(value) 54 | } else { 55 | writer.writeElement(TLInt32(value)) 56 | } 57 | } 58 | 59 | override fun encodeByte(value: Byte) { 60 | if (writer is BytesElementWriter) { 61 | writer.writeElement(value) 62 | } else { 63 | unsupportedPrimitive("Byte") 64 | } 65 | } 66 | override fun encodeLong(value: Long) = writer.writeElement(value.typedLanguage) 67 | override fun encodeDouble(value: Double) = writer.writeElement(value.typedLanguage) 68 | override fun encodeString(value: String) = writer.writeElement(value.typedLanguage) 69 | override fun encodeBoolean(value: Boolean) = writer.writeElement(value.typedLanguage) 70 | override fun encodeNull() = writer.writeElement(null.typedLanguage) 71 | override fun encodeInline(descriptor: SerialDescriptor): Encoder { 72 | return TLEncoder(tl, writer, parentDescriptor = descriptor) 73 | } 74 | 75 | override fun beginCollection( 76 | descriptor: SerialDescriptor, 77 | collectionSize: Int 78 | ): CompositeEncoder { 79 | return when (descriptor.kind as StructureKind) { 80 | StructureKind.LIST -> intEncoder(descriptor, collectionSize) 81 | ?: bytesEncoder(descriptor) 82 | ?: TLEncoder(tl, ListElementWriter(writer)) 83 | StructureKind.MAP -> throw SerializationException("TL doesn't support maps") 84 | else -> error("Unknown collection kind ${descriptor.kind}") 85 | } 86 | } 87 | 88 | private fun intEncoder( 89 | descriptor: SerialDescriptor, 90 | collectionSize: Int 91 | ): TLEncoder? { 92 | val underlying = descriptor.getElementDescriptor(index = 0) 93 | if (underlying.kind != PrimitiveKind.INT) return null 94 | 95 | val sizeAnnotation = parentDescriptor 96 | ?.getElementAnnotations(elementIndex) 97 | ?.filterIsInstance() 98 | ?.firstOrNull() 99 | val size = sizeAnnotation?.bits ?: return null 100 | 101 | require(collectionSize * Int.SIZE_BITS == size) { 102 | "int$size expected, but int${collectionSize * Int.SIZE_BITS} got" 103 | } 104 | 105 | val writer = IntElementWriter(writer, TLIntDescriptor.of(size)) 106 | return TLEncoder(tl, writer) 107 | } 108 | 109 | private fun bytesEncoder( 110 | descriptor: SerialDescriptor 111 | ): TLEncoder? { 112 | val underlying = descriptor.getElementDescriptor(index = 0) 113 | if (underlying.kind != PrimitiveKind.BYTE) return null 114 | val writer = BytesElementWriter(writer) 115 | return TLEncoder(tl, writer) 116 | } 117 | 118 | override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { 119 | if (descriptor.kind == PolymorphicKind.SEALED) return this 120 | 121 | when (descriptor.kind as? StructureKind) { 122 | StructureKind.CLASS, StructureKind.OBJECT -> { 123 | val crc32 = descriptor.crc32 124 | val rpcCall = descriptor.tlRpc 125 | 126 | val bare = parentDescriptor 127 | ?.getElementAnnotations(elementIndex) 128 | ?.filterIsInstance() 129 | ?.firstOrNull() 130 | 131 | return when { 132 | crc32 != null && rpcCall != null -> throw SerializationException("You should not annotate class with both @Crc32 or @TLRpcCall, use @Crc32 for constructors and @TLRpcCall for functions") 133 | bare != null -> TLEncoder(tl, ConstructorElementWriter(crc32 = null, writer), descriptor) 134 | crc32 != null -> TLEncoder(tl, ConstructorElementWriter(crc32.value, writer), descriptor) 135 | rpcCall != null -> TLEncoder(tl, FunctionElementWriter(rpcCall.crc32, writer), descriptor) 136 | else -> throw SerializationException("Your class ${descriptor.serialName} should be annotated with @Crc32 for constructors or @TLRpcCall for functions to make it compatible with TL") 137 | } 138 | } 139 | else -> error("Unsupported structure kind ${descriptor.kind}") 140 | } 141 | } 142 | 143 | override fun endStructure(descriptor: SerialDescriptor) { 144 | if (descriptor.kind == PolymorphicKind.SEALED) return 145 | writer.endStructure() 146 | } 147 | 148 | override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean { 149 | elementIndex = index 150 | if (descriptor.kind != PolymorphicKind.SEALED) return true 151 | return index > 0 152 | } 153 | 154 | override fun encodeChar(value: Char) = unsupportedPrimitive("Char") 155 | override fun encodeFloat(value: Float) = unsupportedPrimitive("Float") 156 | override fun encodeShort(value: Short) = unsupportedPrimitive("Short") 157 | override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = 158 | throw SerializationException("TL doesn't support enums") 159 | 160 | private fun unsupportedPrimitive( 161 | name: String 162 | ): Nothing = throw SerializationException("TL doesn't support $name. All supported primitives are: Boolean, Int, Long, String, Double") 163 | 164 | override val serializersModule get() = tl.serializersModule 165 | } 166 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/extensions/SerialDescriptor.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalSerializationApi::class) 2 | 3 | package kotl.serialization.extensions 4 | 5 | import kotl.core.descriptor.* 6 | import kotl.serialization.annotation.Crc32 7 | import kotl.serialization.annotation.TLBare 8 | import kotl.serialization.annotation.TLRpc 9 | import kotl.serialization.annotation.TLSize 10 | import kotlinx.serialization.ExperimentalSerializationApi 11 | import kotlinx.serialization.SerializationException 12 | import kotlinx.serialization.descriptors.* 13 | 14 | internal val SerialDescriptor.tlRpc: TLRpc? 15 | get() = annotations.filterIsInstance().firstOrNull() 16 | 17 | internal val SerialDescriptor.crc32: Crc32? 18 | get() = annotations.filterIsInstance().firstOrNull() 19 | 20 | 21 | public fun SerialDescriptor.asTLDescriptor( 22 | annotations: List = this.annotations 23 | ): TLExpressionDescriptor = when (kind) { 24 | PolymorphicKind.SEALED, 25 | StructureKind.OBJECT, 26 | StructureKind.CLASS -> { 27 | if (isInline) { 28 | getElementDescriptor(index = 0).asTLDescriptor(getElementAnnotations(index = 0)) 29 | } else { 30 | asTypeDescriptor(annotations) 31 | } 32 | } 33 | StructureKind.OBJECT -> asTypeDescriptor(annotations) 34 | StructureKind.LIST -> asVectorDescriptor(annotations) 35 | PrimitiveKind.BOOLEAN -> TLBooleanDescriptor 36 | PrimitiveKind.INT -> TLInt32Descriptor 37 | PrimitiveKind.LONG -> TLInt64Descriptor 38 | PrimitiveKind.DOUBLE -> TLDoubleDescriptor 39 | PrimitiveKind.STRING -> TLStringDescriptor 40 | PrimitiveKind.BYTE -> unsupportedPrimitive("Byte") 41 | PrimitiveKind.CHAR -> unsupportedPrimitive("Char") 42 | PrimitiveKind.FLOAT -> unsupportedPrimitive("Float") 43 | PrimitiveKind.SHORT -> unsupportedPrimitive("Short") 44 | SerialKind.ENUM -> error("TL doesn't support enums") 45 | StructureKind.MAP -> error("TL doesn't support maps") 46 | else -> error("Unsupported structure kind $kind") 47 | } 48 | 49 | private fun unsupportedPrimitive( 50 | name: String 51 | ): Nothing = throw SerializationException("TL doesn't support $name. All supported primitives are: Boolean, Int, Long, String, Double") 52 | 53 | private fun SerialDescriptor.asTypeDescriptor( 54 | annotations: List 55 | ): TLTypeDescriptor { 56 | if (tlRpc != null) { 57 | error("Cannot create type descriptor for @RpcCall, it's not intended to be deserialized") 58 | } 59 | 60 | if (kind == PolymorphicKind.SEALED) { 61 | val sealed = getElementDescriptor(index = 1) 62 | val constructors = sealed.elementDescriptors.map { descriptor -> 63 | descriptor.asTLDescriptor() 64 | }.flatMap { (it as TLTypeDescriptor.Boxed).constructors } 65 | return TLTypeDescriptor(constructors) 66 | } 67 | 68 | val crc32 = if (annotations.any { it is TLBare }) { 69 | null 70 | } else { 71 | this.crc32?.value ?: error("Your class $serialName should be annotated with @Crc32 for constructors or @TLRpcCall for functions to make it compatible with TL") 72 | } 73 | 74 | val parameters = elementDescriptors.mapIndexed { i, descriptor -> 75 | descriptor.asTLDescriptor(getElementAnnotations(i)) 76 | } 77 | 78 | crc32 ?: return TLTypeDescriptor.Bare(parameters) 79 | 80 | return TLTypeDescriptor.Boxed( 81 | constructors = listOf( 82 | element = TLConstructorDescriptor(crc32, parameters) 83 | ) 84 | ) 85 | } 86 | 87 | private fun SerialDescriptor.asVectorDescriptor(annotations: List): TLExpressionDescriptor { 88 | val elementDescriptor = getElementDescriptor(index = 0) 89 | 90 | if (elementDescriptor.kind == PrimitiveKind.INT) { 91 | val size = annotations.filterIsInstance().firstOrNull() 92 | if (size != null) return TLIntDescriptor.of(size.bits) 93 | } 94 | 95 | if (elementDescriptor.kind == PrimitiveKind.BYTE) { 96 | return TLBytesDescriptor 97 | } 98 | 99 | val underlying = elementDescriptor.asTLDescriptor() 100 | return TLVectorDescriptor(underlying) 101 | } 102 | -------------------------------------------------------------------------------- /serialization/src/commonMain/kotlin/kotl/serialization/int/Int128.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization.int 2 | 3 | import kotl.serialization.annotation.TLSize 4 | import kotlinx.serialization.ExperimentalSerializationApi 5 | import kotlinx.serialization.KSerializer 6 | import kotlinx.serialization.Serializable 7 | import kotlinx.serialization.descriptors.SerialDescriptor 8 | import kotlinx.serialization.encoding.Decoder 9 | import kotlinx.serialization.encoding.Encoder 10 | import kotlin.jvm.JvmInline 11 | 12 | @Serializable(with = Int128.Serializer::class) 13 | public data class Int128( 14 | @TLSize(bits = 128) 15 | public val data: IntArray 16 | ) { 17 | init { 18 | require(data.size == 4) { "int128 expected, but int${data.size * Int.SIZE_BITS} got" } 19 | } 20 | 21 | override fun equals(other: Any?): Boolean { 22 | if (this === other) return true 23 | if (other !is Int128) return false 24 | return data.contentEquals(other.data) 25 | } 26 | 27 | override fun hashCode(): Int { 28 | return data.contentHashCode() 29 | } 30 | 31 | public object Serializer : KSerializer { 32 | 33 | @OptIn(ExperimentalSerializationApi::class) 34 | override val descriptor: SerialDescriptor = SerialDescriptor( 35 | serialName = "kotl.serialization.int.Int128", 36 | original = Int128Serializable.serializer().descriptor 37 | ) 38 | 39 | override fun deserialize(decoder: Decoder): Int128 { 40 | val serializable = decoder.decodeSerializableValue( 41 | deserializer = Int128Serializable.serializer() 42 | ) 43 | return Int128(serializable.data) 44 | } 45 | 46 | override fun serialize(encoder: Encoder, value: Int128) { 47 | val serializable = Int128Serializable(value.data) 48 | encoder.encodeSerializableValue( 49 | serializer = Int128Serializable.serializer(), 50 | value = serializable 51 | ) 52 | } 53 | } 54 | } 55 | 56 | @Serializable 57 | @JvmInline 58 | private value class Int128Serializable( 59 | @TLSize(bits = 128) 60 | val data: IntArray 61 | ) 62 | -------------------------------------------------------------------------------- /serialization/src/jvmMain/kotlin/TLMain.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("ArrayInDataClass") 2 | 3 | package kotl.serialization 4 | 5 | import kotl.core.descriptor.* 6 | import kotl.serialization.annotation.Crc32 7 | import kotl.serialization.annotation.TLBare 8 | import kotl.serialization.annotation.TLSize 9 | import kotl.serialization.bare.Bare 10 | import kotl.serialization.bytes.Bytes 11 | import kotl.serialization.int.Int128 12 | import kotlinx.serialization.Serializable 13 | import kotlinx.serialization.decodeFromByteArray 14 | import kotlinx.serialization.encodeToByteArray 15 | 16 | @Serializable 17 | @Crc32(value = 0x2d84d5f5_u) 18 | private data class GetUserRequest( 19 | val ids: List 20 | ) 21 | 22 | @Serializable 23 | public sealed interface InputUserType 24 | 25 | @Serializable 26 | @Crc32(value = 0xb98886cf_u) 27 | private data object inputUserEmpty : InputUserType 28 | 29 | @Serializable 30 | @Crc32(value = 0xf7c1b13f_u) 31 | private data object inputUserSelf : InputUserType 32 | 33 | @Serializable 34 | @Crc32(value = 0xf21158c6_u) 35 | private data class inputUser( 36 | val userId: Long, 37 | val accessHash: Long, 38 | val username: String, 39 | val pq: ExchangePQ 40 | ) : InputUserType 41 | 42 | @Serializable 43 | @Crc32(value = 0xf0f0f0f_u) 44 | private data class ExchangePQ( 45 | val nonce: Int128, 46 | val pq: Bytes, 47 | @TLBare val publicKey: PublicKey 48 | ) 49 | 50 | @Serializable 51 | private data class PublicKey( 52 | val n: Bytes, 53 | val e: Bytes 54 | ) 55 | 56 | //private fun main() { 57 | // val descriptor = InputUserType.serializer().descriptor.asTLDescriptor() 58 | // println(descriptor.prettyString()) 59 | // 60 | // val manual = buildTLTypeDescriptor { 61 | // constructor(0xb98886cf_u) 62 | // constructor(0xf7c1b13f_u) 63 | // constructor(0xf21158c6_u) { 64 | // longParameter() 65 | // longParameter() 66 | // stringParameter() 67 | // } 68 | // } 69 | // println(manual.prettyString()) 70 | //} 71 | 72 | private fun TLExpressionDescriptor.prettyString(indent: String = ""): String = when (this) { 73 | TLBooleanDescriptor -> indent + "boolean" + '\n' 74 | TLDoubleDescriptor -> indent + "double" + '\n' 75 | TLInt32Descriptor -> indent + "int" + '\n' 76 | TLInt64Descriptor -> indent + "long" + '\n' 77 | TLInt128Descriptor -> indent + "int128" + '\n' 78 | TLNullDescriptor -> indent + "null" + '\n' 79 | TLStringDescriptor -> indent + "string" + '\n' 80 | TLBytesDescriptor -> indent + "bytes" + '\n' 81 | is TLTypeDescriptor -> buildString { 82 | appendLine(indent + "type: ") 83 | when (this@prettyString) { 84 | is TLTypeDescriptor.Bare -> { 85 | for (parameter in parameters) { 86 | append(parameter.prettyString(indent = "$indent ")) 87 | } 88 | } 89 | is TLTypeDescriptor.Boxed -> constructors.forEach { constructor -> 90 | appendLine("$indent constructor: ${constructor.crc32}") 91 | for (parameter in constructor.parameters) { 92 | append(parameter.prettyString(indent = "$indent ")) 93 | } 94 | } 95 | } 96 | } 97 | is TLVectorDescriptor -> buildString { 98 | appendLine(indent + "vector of: ") 99 | append(underlying.prettyString(indent = "$indent ")) 100 | } 101 | } 102 | 103 | private fun main() { 104 | val pq = ExchangePQ( 105 | nonce = Int128(intArrayOf(0, 0, 0, 0)), 106 | pq = Bytes(byteArrayOf(1, 1, 1, 1)), 107 | publicKey = PublicKey( 108 | n = Bytes(byteArrayOf(2, 2, 2, 2)), 109 | e = Bytes(byteArrayOf(3, 3, 3, 3)) 110 | ) 111 | ) 112 | val users = listOf( 113 | inputUserEmpty, 114 | inputUserSelf, 115 | inputUser(userId = 0, accessHash = 0xff, username = "*��r��/", pq = pq) 116 | ) 117 | val request = GetUserRequest(users) 118 | 119 | println("INITIAL: $request") 120 | println("*��r��/".length) 121 | println("*��r��/".encodeToByteArray().joinToString(separator = " ") { it.toUByte().toString(16).padStart(2, '0') }) 122 | val bytes = TL.encodeToByteArray(request) 123 | println("BYTES: ${bytes.toHexString()}") 124 | val deserialized: GetUserRequest = TL.decodeFromByteArray(bytes) 125 | println("RESULT: $deserialized") 126 | 127 | println() 128 | } 129 | 130 | @OptIn(ExperimentalUnsignedTypes::class) 131 | private fun ByteArray.toHexString() = 132 | asUByteArray().joinToString(" ") { it.toString(16).padStart(2, '0') } 133 | -------------------------------------------------------------------------------- /serialization/src/jvmMain/kotlin/TLMain2.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization 2 | 3 | import kotl.serialization.annotation.Crc32 4 | import kotl.serialization.annotation.TLSize 5 | import kotl.serialization.extensions.asTLDescriptor 6 | import kotlinx.serialization.Serializable 7 | import kotlinx.serialization.serializer 8 | 9 | @Serializable 10 | @Crc32(value = 0x05162463_u) 11 | private class TLResponsePQ( 12 | @TLSize(bits = 128) 13 | val nonce: IntArray, 14 | @TLSize(bits = 128) 15 | val serverNonce: IntArray, 16 | val pq: String, 17 | val serverPublicKey: List 18 | ) 19 | 20 | private fun main() { 21 | println(serializer().descriptor.asTLDescriptor()) 22 | } 23 | -------------------------------------------------------------------------------- /serialization/src/jvmMain/kotlin/TLMain3.kt: -------------------------------------------------------------------------------- 1 | package kotl.serialization 2 | 3 | import kotl.serialization.annotation.Crc32 4 | import kotl.serialization.annotation.TLSize 5 | import kotl.serialization.int.Int128 6 | import kotlinx.serialization.Serializable 7 | import kotlinx.serialization.decodeFromByteArray 8 | import kotlinx.serialization.encodeToByteArray 9 | 10 | @Serializable 11 | @Crc32(value = 0xf0f0f0f_u) 12 | private data class ExchangePQ2(val nonce: Int128) 13 | 14 | private fun main() { 15 | val data = ExchangePQ2( 16 | nonce = Int128(intArrayOf(0, 0, 0, 0)) 17 | ) 18 | println("DATA: $data") 19 | val bytes = TL.encodeToByteArray(data) 20 | println("BYTES: ${bytes.joinToString(separator = " ") { it.toUByte().toString(16).padStart(2, '0') }}") 21 | val decoded = TL.decodeFromByteArray(bytes) 22 | println("DECODED: $decoded") 23 | } 24 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | pluginManagement { 4 | repositories { 5 | mavenCentral() 6 | google() 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | dependencyResolutionManagement { 12 | repositories { 13 | mavenCentral() 14 | google() 15 | } 16 | } 17 | 18 | includeBuild("build-logic") 19 | 20 | include( 21 | ":core", 22 | ":serialization", 23 | ":schema", 24 | ":libs:stdlib-extensions", 25 | ":libs:string-parser", 26 | ) 27 | 28 | rootProject.name = "koTL" 29 | --------------------------------------------------------------------------------