├── .gitignore ├── .idea ├── .gitignore ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── misc.xml ├── uiDesigner.xml └── vcs.xml ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── commonMain └── kotlin │ └── io │ └── github │ └── dingyi222666 │ └── luaparser │ ├── lexer │ ├── LuaLexer.kt │ └── WrapperLuaLexer.kt │ ├── parser │ ├── LuaParser.kt │ ├── ast │ │ ├── node │ │ │ ├── AbstractNode.kt │ │ │ ├── blockNode.kt │ │ │ ├── expressionNode.kt │ │ │ └── statementNode.kt │ │ └── visitor │ │ │ └── visitor.kt │ └── version.kt │ ├── semantic │ ├── SemanticAnalyzer.kt │ ├── comment │ │ └── CommentProcessor.kt │ ├── symbol │ │ └── SymbolTable.kt │ └── types │ │ ├── Type.kt │ │ ├── TypeAnnotationParser.kt │ │ ├── TypeContext.kt │ │ └── TypeInferer.kt │ ├── source │ └── AST2Lua.kt │ └── util │ ├── TrieTree.kt │ ├── atomic.kt │ └── default.kt ├── commonTest └── kotlin │ └── parser.common.kt ├── jsTest └── kotlin │ └── parser.js.kt ├── jvmTest ├── .gitignore └── kotlin │ └── parser.jvm.kt └── nativeTest └── kotlin └── parser.native.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store 43 | 44 | src/main/kotlin-antlr 45 | 46 | .kotlin 47 | /kotlin-js-store/yarn.lock 48 | .idea/artifacts 49 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 17 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-parser 2 | 3 | ## _work in progress_ 4 | 5 | A Lua 5.3 Lexer & Parser written in pure Kotlin. 6 | 7 | ## Features 8 | 9 | - [X] Kotlin Multiplatform support (JVM / JS / Native) 10 | - [x] Parse source to AST 11 | - [x] Transform AST to source code 12 | - [ ] Semantic analysis. Provide type information (Work in progress) 13 | 14 | ## Usage 15 | 16 | - Add the dependency to your gradle file 17 | 18 | ```kotlin 19 | implementation("io.github.dingyi222666:luaparser:1.0.3") 20 | ``` 21 | 22 | Ok. Use it like this: 23 | 24 | ```kotlin 25 | val lexer = LuaLexer("print('hello world')") 26 | val parser = LuaParser() 27 | 28 | val root = parser.parse(lexer) 29 | 30 | println(AST2Lua().asCode(root)) 31 | ``` 32 | 33 | More usage coming soon. 34 | 35 | ## Special thanks 36 | 37 | [GavinHigham/lpil53](https://github.com/GavinHigham/lpil53) 38 | 39 | [fstirlitz/luaparse](https://github.com/fstirlitz/luaparse) 40 | 41 | [Rosemose/sora-editor](https://github.com/Rosemoe/sora-editor/blob/main/language-java/src/main/java/io/github/rosemoe/sora/langs/java/JavaTextTokenizer.java) -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalKotlinGradlePluginApi::class) 2 | 3 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 4 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 5 | 6 | plugins { 7 | kotlin("multiplatform") version "2.0.20" 8 | java 9 | id("com.vanniktech.maven.publish") version "0.29.0" 10 | id("maven-publish") 11 | signing 12 | } 13 | 14 | group = "io.github.dingyi222666" 15 | version = "1.0.3" 16 | 17 | kotlin { 18 | jvm { 19 | compilerOptions { 20 | jvmTarget.set(JvmTarget.JVM_11) 21 | } 22 | withJava() 23 | } 24 | 25 | macosX64() 26 | macosArm64() 27 | linuxArm64() 28 | linuxX64() 29 | mingwX64() 30 | 31 | js { 32 | browser { 33 | testTask { 34 | useKarma { 35 | useChromeHeadless() 36 | webpackConfig.cssSupport { 37 | enabled.set(true) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | sourceSets { 45 | commonMain { 46 | dependencies { 47 | implementation(kotlin("stdlib")) 48 | implementation(kotlin("test")) 49 | 50 | } 51 | } 52 | commonTest { 53 | dependencies { 54 | 55 | implementation(kotlin("test-annotations-common")) 56 | 57 | } 58 | } 59 | 60 | jvmTest { 61 | dependencies { 62 | implementation(kotlin("test-junit")) 63 | } 64 | // add java to src 65 | } 66 | 67 | jsTest { 68 | dependencies { 69 | implementation(kotlin("test-js")) 70 | } 71 | } 72 | 73 | nativeTest { 74 | dependencies { 75 | // implementation(kotlin("test-native")) 76 | } 77 | } 78 | } 79 | 80 | jvmToolchain(11) 81 | } 82 | 83 | 84 | 85 | mavenPublishing { 86 | publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.S01) 87 | 88 | signAllPublications() 89 | 90 | coordinates("io.github.dingyi222666", "luaparser", "1.0.3") 91 | 92 | pom { 93 | name.set("luaparser") 94 | description.set("A Lua 5.3 Lexer & Parser written in pure Kotlin.") 95 | inceptionYear.set("2023") 96 | url.set("https://github.com/dingyi222666/luaparser") 97 | licenses { 98 | license { 99 | name.set("The Apache License, Version 2.0") 100 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 101 | distribution.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 102 | } 103 | } 104 | developers { 105 | developer { 106 | id.set("dingyi222666") 107 | name.set("dingyi222666") 108 | url.set("https://github.com/dingyi222666") 109 | } 110 | } 111 | scm { 112 | url.set("https://github.com/dingyi222666/lua-parser") 113 | connection.set("scm:git:git://github.com/dingyi222666/lua-parser.git") 114 | developerConnection.set("scm:git:ssh://git@github.com/dingyi222666/lua-parser.git") 115 | } 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | 2 | #Gradle 3 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" 4 | 5 | #Kotlin 6 | kotlin.code.style=official 7 | kotlin.js.compiler=ir 8 | kotlin.js.generate.executable.default=false 9 | 10 | #MPP 11 | kotlin.mpp.enableCInteropCommonization=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingyi222666/lua-parser/04e2f8a1fb6e9113192cfcd412c1729b7293c523/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.10-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /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/master/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 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /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 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | gradlePluginPortal() 5 | mavenCentral() 6 | } 7 | 8 | 9 | } 10 | 11 | dependencyResolutionManagement { 12 | repositories { 13 | google() 14 | mavenCentral() 15 | } 16 | } 17 | 18 | rootProject.name = "luaparser" 19 | 20 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/lexer/LuaLexer.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.lexer 2 | 3 | import io.github.dingyi222666.luaparser.util.TrieTree 4 | 5 | 6 | class LuaLexer( 7 | private val source: CharSequence 8 | ) : Iterator> { 9 | 10 | private val bufferLen = source.length; 11 | 12 | private var offset = 0 13 | 14 | private var tokenType: LuaTokenTypes = 15 | LuaTokenTypes.WHITE_SPACE 16 | 17 | var tokenLine = 1 18 | private set 19 | var tokenColumn = 0 20 | private set 21 | var index = 0 22 | private set 23 | 24 | var tokenLength = 0 25 | private set 26 | 27 | val tokenText: CharSequence 28 | get() { 29 | return source.subSequence(index, index + tokenLength) 30 | } 31 | 32 | fun nextToken(): LuaTokenTypes { 33 | return nextTokenInternal().also { tokenType = it } 34 | } 35 | 36 | private fun nextTokenInternal(): LuaTokenTypes { 37 | run { 38 | var r = false 39 | for (i in offset..= bufferLen) { 64 | tokenLength = 0 65 | return LuaTokenTypes.EOF 66 | } 67 | 68 | val ch = source[offset] 69 | tokenLength = 1 70 | 71 | return when { 72 | isWhitespace(ch) -> { 73 | var chLocal = '\t' 74 | while (offset + tokenLength < bufferLen && chatAtOrNull(offset + tokenLength) 75 | ?.also { 76 | chLocal = it 77 | }?.let { 78 | isWhitespace(it) 79 | } == true 80 | ) { 81 | if (chLocal == '\r' || chLocal == '\n') { 82 | break 83 | } 84 | tokenLength++ 85 | } 86 | LuaTokenTypes.WHITE_SPACE 87 | } 88 | 89 | isIdentifierStart(ch) -> scanIdentifier(ch) 90 | isPrimeDigit(ch) -> scanNumber(ch) 91 | ch == '\n' -> LuaTokenTypes.NEW_LINE 92 | ch == '\r' -> { 93 | scanNewline() 94 | LuaTokenTypes.NEW_LINE 95 | } 96 | 97 | ch == ';' -> LuaTokenTypes.SEMI 98 | ch == '(' -> LuaTokenTypes.LPAREN 99 | ch == ')' -> LuaTokenTypes.RPAREN 100 | ch == '[' -> { 101 | val next = chatAtOrNull() ?: return LuaTokenTypes.RBRACK 102 | 103 | if (next != '=' && next != '[') { 104 | return LuaTokenTypes.LBRACK 105 | } 106 | 107 | 108 | return scanLongString() 109 | 110 | } 111 | 112 | ch == ']' -> LuaTokenTypes.RBRACK 113 | ch == '{' -> LuaTokenTypes.LCURLY 114 | ch == '}' -> LuaTokenTypes.RCURLY 115 | ch == ',' -> LuaTokenTypes.COMMA 116 | ch == '!' -> LuaTokenTypes.NOT 117 | ch == '+' -> scanTwoOperator( 118 | LuaTokenTypes.PLUS, 119 | LuaTokenTypes.ADD_ASSIGN, '=' 120 | ) 121 | 122 | ch == '*' -> scanTwoOperator( 123 | LuaTokenTypes.MULT, 124 | LuaTokenTypes.MUL_ASSIGN, '=' 125 | ) 126 | 127 | ch == '/' -> scanDIV() 128 | ch == '=' -> scanTwoOperator( 129 | LuaTokenTypes.ASSIGN, 130 | LuaTokenTypes.EQ, '=' 131 | ) 132 | 133 | ch == '^' -> LuaTokenTypes.EXP 134 | ch == '%' -> LuaTokenTypes.MOD 135 | ch == '~' -> LuaTokenTypes.BIT_TILDE 136 | ch == '&' -> LuaTokenTypes.BIT_AND 137 | ch == '|' -> LuaTokenTypes.BIT_OR 138 | ch == '>' -> scanTwoOperator( 139 | LuaTokenTypes.GT, 140 | LuaTokenTypes.GE, '=' 141 | ) 142 | 143 | ch == '<' -> scanTwoOperator( 144 | LuaTokenTypes.LT, 145 | LuaTokenTypes.LE, '=' 146 | ) 147 | 148 | ch == '.' -> { 149 | val next = chatAtOrNull() ?: return LuaTokenTypes.DOT 150 | 151 | when { 152 | isPrimeDigit(next) -> { 153 | scanPrimeDigit() 154 | LuaTokenTypes.NUMBER 155 | } 156 | 157 | next == '.' -> { 158 | tokenLength++ 159 | LuaTokenTypes.CONCAT 160 | } 161 | 162 | else -> LuaTokenTypes.DOT 163 | } 164 | 165 | 166 | } 167 | 168 | ch == '"' || ch == '\'' -> scanString(ch) 169 | ch == '#' -> LuaTokenTypes.GETN 170 | ch == ':' -> scanTwoOperator( 171 | LuaTokenTypes.COLON, 172 | LuaTokenTypes.DOUBLE_COLON, ':' 173 | ) 174 | 175 | ch == '-' -> { 176 | val next = chatAtOrNull() ?: return LuaTokenTypes.MINUS 177 | when (next) { 178 | '-' -> { 179 | tokenLength++ 180 | scanComment() 181 | } 182 | 183 | '=' -> { 184 | tokenLength++ 185 | LuaTokenTypes.SUB_ASSIGN 186 | } 187 | 188 | else -> LuaTokenTypes.MINUS 189 | } 190 | } 191 | 192 | else -> LuaTokenTypes.BAD_CHARACTER 193 | } 194 | 195 | } 196 | 197 | private fun scanIdentifier(char: Char): LuaTokenTypes { 198 | var ch = char 199 | var n: TrieTree.Node? = keywords.root.map.get(ch) 200 | while (offset + tokenLength < bufferLen && isIdentifierPart( 201 | charAt(offset + tokenLength).also { ch = it }) 202 | ) { 203 | tokenLength++ 204 | n = n?.map?.get(ch) 205 | } 206 | return n?.token ?: LuaTokenTypes.NAME 207 | } 208 | 209 | 210 | private fun scanString(start: Char): LuaTokenTypes { 211 | var finish = false 212 | 213 | while (offset + tokenLength < bufferLen) { 214 | val ch = charAt() 215 | when (ch) { 216 | start -> { 217 | finish = true 218 | break 219 | } 220 | // escape 221 | '\\' -> { 222 | val next = charAt(offset + tokenLength + 1) 223 | 224 | when (next) { 225 | 'a', 'b', 'f', 'n', 'r', 't', 'v', '\'', '"', '\\', '\n', '\r' -> { 226 | tokenLength++ 227 | } 228 | 229 | 'z' -> { 230 | tokenLength += 2 231 | } 232 | 233 | 'x' -> { 234 | tokenLength += 3 235 | } 236 | } 237 | } 238 | 239 | '\n', '\r' -> throw IllegalStateException("Unfinished string at <$tokenLine, ${tokenColumn}>") 240 | 241 | } 242 | 243 | tokenLength++ 244 | } 245 | 246 | if (!finish) { 247 | throw IllegalStateException("Unfinished string at <$tokenLine, ${tokenColumn}>") 248 | } 249 | 250 | tokenLength++ 251 | 252 | return LuaTokenTypes.STRING 253 | } 254 | 255 | 256 | private fun scanComment(): LuaTokenTypes { 257 | if (tokenLength + offset == bufferLen) { 258 | return LuaTokenTypes.SHORT_COMMENT 259 | } 260 | 261 | val next = charAt() 262 | 263 | when (next) { 264 | '[' -> { 265 | scanLongString() 266 | return LuaTokenTypes.BLOCK_COMMENT 267 | } 268 | '-' -> { 269 | // This is the third dash, so it's a doc comment 270 | tokenLength++ 271 | 272 | // Scan first line content until newline 273 | while (offset + tokenLength < bufferLen) { 274 | val ch = charAt() 275 | if (ch == '\n' || ch == '\r') { 276 | if (ch == '\r' && offset + tokenLength + 1 < bufferLen && source[offset + tokenLength + 1] == '\n') { 277 | tokenLength += 2 278 | } else { 279 | tokenLength++ 280 | } 281 | break 282 | } 283 | tokenLength++ 284 | } 285 | 286 | // Look for continuation lines 287 | while (offset + tokenLength < bufferLen) { 288 | var pos = offset + tokenLength 289 | 290 | // Skip whitespace at start of line 291 | while (pos < bufferLen && source[pos] != '\n' && source[pos] != '\r' && isNotNewLineWhiteSpace(source[pos])) { 292 | pos++ 293 | } 294 | 295 | // Check for doc comment continuation (---) 296 | if (pos + 2 < bufferLen && 297 | source[pos] == '-' && 298 | source[pos + 1] == '-' && 299 | source[pos + 2] == '-') { 300 | 301 | pos += 3 302 | 303 | // Include this line in token 304 | while (pos < bufferLen) { 305 | if (source[pos] == '\n' || source[pos] == '\r') { 306 | if (source[pos] == '\r' && pos + 1 < bufferLen && source[pos + 1] == '\n') { 307 | pos += 2 308 | } else { 309 | pos++ 310 | } 311 | break 312 | } 313 | pos++ 314 | } 315 | 316 | tokenLength = pos - offset 317 | } else { 318 | println() 319 | break 320 | } 321 | } 322 | 323 | return LuaTokenTypes.DOC_COMMENT 324 | } 325 | else -> { 326 | // Regular comment 327 | while (offset + tokenLength < bufferLen) { 328 | val ch = charAt() 329 | if (ch == '\n' || ch == '\r') { 330 | if (ch == '\r' && offset + tokenLength + 1 < bufferLen && source[offset + tokenLength + 1] == '\n') { 331 | tokenLength += 2 332 | } else { 333 | tokenLength++ 334 | } 335 | break 336 | } 337 | tokenLength++ 338 | } 339 | return LuaTokenTypes.SHORT_COMMENT 340 | } 341 | } 342 | } 343 | 344 | private fun scanLongString(): LuaTokenTypes { 345 | tokenLength++ 346 | val skipCount = scanLongStringSkipComment() 347 | 348 | while (offset + tokenLength < bufferLen && charAt() != ']') { 349 | tokenLength++ 350 | } 351 | 352 | tokenLength++ 353 | 354 | if (scanLongStringSkipComment() != skipCount) { 355 | throw IllegalStateException("Unfinished long string at <$tokenLine, ${tokenColumn}>") 356 | } 357 | 358 | // add \] 359 | tokenLength++ 360 | 361 | return LuaTokenTypes.LONG_STRING 362 | } 363 | 364 | private fun scanLongStringSkipComment(): Int { 365 | var count = 0 366 | 367 | while (offset + tokenLength < bufferLen && charAt() == '=') { 368 | tokenLength++ 369 | count++ 370 | } 371 | 372 | return count 373 | } 374 | 375 | private fun scanDIV(): LuaTokenTypes { 376 | val next = charAt() 377 | 378 | return when (next) { 379 | '=' -> { 380 | tokenLength++ 381 | LuaTokenTypes.DIV_ASSIGN 382 | } 383 | 384 | '/' -> { 385 | tokenLength++ 386 | scanTwoOperator( 387 | LuaTokenTypes.DOUBLE_DIV, 388 | LuaTokenTypes.DOUBLE_DIV_ASSIGN, '=' 389 | ) 390 | } 391 | 392 | else -> LuaTokenTypes.DIV 393 | 394 | } 395 | } 396 | 397 | private fun scanTwoOperator( 398 | first: LuaTokenTypes, second: LuaTokenTypes, operator: Char 399 | ): LuaTokenTypes { 400 | if (tokenLength + offset == bufferLen) { 401 | // The operator is the last token in the buffer 402 | return first 403 | } 404 | 405 | if (charAt() == operator) { 406 | tokenLength++ 407 | return second 408 | } 409 | 410 | return first 411 | } 412 | 413 | @Suppress("SameReturnValue") 414 | private fun scanNumber(char: Char): LuaTokenTypes { 415 | if (tokenLength + offset == bufferLen) { 416 | // The number is the last token in the buffer 417 | return LuaTokenTypes.NUMBER 418 | } 419 | 420 | // check hex number 421 | 422 | var ch = char 423 | 424 | if (ch == '0' && charAt() == 'x') { 425 | tokenLength++ 426 | 427 | } 428 | 429 | scanDigit() 430 | 431 | if (offset + tokenLength == bufferLen) { 432 | // if the number is the last token, return it 433 | return LuaTokenTypes.NUMBER 434 | } 435 | 436 | ch = charAt() 437 | 438 | if (ch != '.') { 439 | // not a decimal point 440 | return LuaTokenTypes.NUMBER 441 | } 442 | 443 | try { 444 | throwIfNeeded() 445 | } catch (e: IllegalStateException) { 446 | return LuaTokenTypes.BAD_CHARACTER 447 | } 448 | 449 | scanDigit() 450 | 451 | return LuaTokenTypes.NUMBER 452 | } 453 | 454 | private fun scanDigit() { 455 | while (offset + tokenLength < bufferLen && isDigit( 456 | charAt() 457 | ) 458 | ) { 459 | tokenLength++ 460 | } 461 | } 462 | 463 | private fun scanPrimeDigit() { 464 | while (offset + tokenLength < bufferLen && isPrimeDigit( 465 | charAt(offset + tokenLength) 466 | ) 467 | ) { 468 | tokenLength++ 469 | } 470 | } 471 | 472 | fun pushBack(length: Int) { 473 | require(length <= tokenLength) { "pushBack length too large" } 474 | tokenLength -= length 475 | } 476 | 477 | private fun throwIfNeeded() { 478 | require(offset + tokenLength < bufferLen) { 479 | "Token too long" 480 | } 481 | } 482 | 483 | private fun scanNewline() { 484 | if (offset + tokenLength < bufferLen && charAt(offset + tokenLength) == '\n') { 485 | tokenLength++ 486 | } 487 | } 488 | 489 | 490 | private fun charAt(i: Int): Char { 491 | return source[i] 492 | } 493 | 494 | private fun charAt(): Char { 495 | return source[offset + tokenLength] 496 | } 497 | 498 | private fun chatAtOrNull(): Char? { 499 | return chatAtOrNull(offset + tokenLength) 500 | } 501 | 502 | private fun chatAtOrNull(i: Int): Char? { 503 | return if (i < bufferLen) source[i] else null 504 | } 505 | 506 | companion object { 507 | val keywords = TrieTree() 508 | 509 | init { 510 | run { 511 | keywords.put( 512 | "and", 513 | LuaTokenTypes.AND 514 | ) 515 | keywords.put( 516 | "or", 517 | LuaTokenTypes.OR 518 | ) 519 | keywords.put( 520 | "default", 521 | LuaTokenTypes.DEFAULT 522 | ) 523 | keywords.put( 524 | "switch", 525 | LuaTokenTypes.SWITCH 526 | ) 527 | keywords.put( 528 | "if", 529 | LuaTokenTypes.IF 530 | ) 531 | keywords.put( 532 | "break", 533 | LuaTokenTypes.BREAK 534 | ) 535 | keywords.put( 536 | "else", 537 | LuaTokenTypes.ELSE 538 | ) 539 | keywords.put( 540 | "while", 541 | LuaTokenTypes.WHILE 542 | ) 543 | keywords.put( 544 | "do", 545 | LuaTokenTypes.DO 546 | ) 547 | keywords.put( 548 | "return", 549 | LuaTokenTypes.RETURN 550 | ) 551 | keywords.put( 552 | "for", 553 | LuaTokenTypes.FOR 554 | ) 555 | keywords.put( 556 | "function", 557 | LuaTokenTypes.FUNCTION 558 | ) 559 | keywords.put( 560 | "local", 561 | LuaTokenTypes.LOCAL 562 | ) 563 | keywords.put( 564 | "true", 565 | LuaTokenTypes.TRUE 566 | ) 567 | keywords.put( 568 | "false", 569 | LuaTokenTypes.FALSE 570 | ) 571 | keywords.put( 572 | "nil", 573 | LuaTokenTypes.NIL 574 | ) 575 | keywords.put( 576 | "continue", 577 | LuaTokenTypes.CONTINUE 578 | ) 579 | keywords.put( 580 | "not", 581 | LuaTokenTypes.NOT 582 | ) 583 | keywords.put( 584 | "in", 585 | LuaTokenTypes.IN 586 | ) 587 | keywords.put( 588 | "then", 589 | LuaTokenTypes.THEN 590 | ) 591 | keywords.put( 592 | "end", 593 | LuaTokenTypes.END 594 | ) 595 | keywords.put( 596 | "repeat", 597 | LuaTokenTypes.REPEAT 598 | ) 599 | keywords.put( 600 | "elseif", 601 | LuaTokenTypes.ELSEIF 602 | ) 603 | keywords.put( 604 | "until", 605 | LuaTokenTypes.UNTIL 606 | ) 607 | keywords.put( 608 | "goto", 609 | LuaTokenTypes.GOTO 610 | ) 611 | keywords.put( 612 | "case", 613 | LuaTokenTypes.CASE 614 | ) 615 | keywords.put( 616 | "when", 617 | LuaTokenTypes.WHEN 618 | ) 619 | } 620 | 621 | } 622 | 623 | private fun isDigit(c: Char): Boolean { 624 | return ((c in '0'..'9') || (c in 'A'..'F') || (c in 'a'..'f')) 625 | } 626 | 627 | private fun isPrimeDigit(c: Char): Boolean { 628 | return (c in '0'..'9') 629 | } 630 | 631 | private fun isWhitespace(c: Char): Boolean { 632 | return (c == '\n' || c == '\r' || c == '\t' || c == ' ' || c == '\u000c') 633 | } 634 | 635 | private fun isNotNewLineWhiteSpace(c: Char): Boolean { 636 | return (c == '\t' || c == ' ' || c == '\u000c') 637 | } 638 | 639 | private fun isIdentifierStart(c: Char): Boolean { 640 | return (c >= '\u0080') || (c in 'a'..'z') || (c in 'A'..'Z') || (c == '_') || (c == '$') 641 | } 642 | 643 | private fun isIdentifierPart(c: Char): Boolean { 644 | return (c in '0'..'9') || isIdentifierStart(c) 645 | } 646 | 647 | } 648 | 649 | override fun hasNext(): Boolean { 650 | return offset + tokenLength < bufferLen 651 | } 652 | 653 | override fun next(): Pair { 654 | val currentToken = nextToken() 655 | 656 | return Pair(currentToken, tokenText.toString()) 657 | } 658 | 659 | } 660 | 661 | 662 | enum class LuaTokenTypes { 663 | SHEBANG_CONTENT, NEW_LINE, WHITE_SPACE, BAD_CHARACTER, 664 | 665 | ADD_ASSIGN, SUB_ASSIGN, MUL_ASSIGN, DIV_ASSIGN, 666 | 667 | /* AND_ASSIGN, 668 | OR_ASSIGN, 669 | XOR_ASSIGN, 670 | MOD_ASSIGN, 671 | LSHIFT_ASSIGN, 672 | RSHIFT_ASSIGN, 673 | URSHIFT_ASSIGN,*/ 674 | DOUBLE_DIV_ASSIGN, 675 | 676 | NAME, NUMBER, PLUS, DOT, MINUS, LBRACK, ASSIGN, RBRACK, GETN, NOT, GT, LT, BIT_TILDE, MULT, MOD, DIV, LPAREN, RPAREN, LCURLY, RCURLY, COMMA, SEMI, COLON, EXP, BIT_AND, BIT_OR, STRING, LONG_STRING, CONCAT, IN, IF, OR, DO, EQ, SHEBANG, NE, GE, BIT_RTRT, LE, BIT_LTLT, DOUBLE_DIV, DOUBLE_COLON, AND, SHORT_COMMENT, ELLIPSIS, END, NIL, LEF, MEAN, FOR, DOC_COMMENT, ELSE, GOTO, CASE, TRUE, THEN, BLOCK_COMMENT, BREAK, LOCAL, FALSE, UNTIL, WHILE, RETURN, REPEAT, ELSEIF, CONTINUE, SWITCH, DEFAULT, FUNCTION, LABEL, WHEN, LAMBDA, EOF 677 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/lexer/WrapperLuaLexer.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.lexer 2 | 3 | /** 4 | * @author: dingyi 5 | * @date: 2023/2/3 6 | * @description: 7 | **/ 8 | class WrapperLuaLexer( 9 | private val currentLexer: LuaLexer 10 | ) { 11 | 12 | private val lastStates = ArrayDeque() 13 | private val currentStates = ArrayDeque(5) 14 | private var currentState = LexerState( 15 | column = currentLexer.tokenColumn, 16 | length = currentLexer.tokenLength, 17 | line = currentLexer.tokenLine, 18 | text = currentLexer.tokenText, 19 | type = LuaTokenTypes.WHITE_SPACE 20 | ) 21 | 22 | fun text() = currentState.text 23 | 24 | fun length() = currentState.length 25 | 26 | 27 | fun line() = currentState.line 28 | 29 | fun column() = currentState.column + 1 30 | 31 | fun advance(): LuaTokenTypes { 32 | if (currentStates.isNotEmpty()) { 33 | currentState = currentStates.removeFirst() 34 | } else { 35 | doAdvance() 36 | currentState.let(lastStates::addFirst) 37 | } 38 | 39 | clearStates() 40 | return currentState.type 41 | } 42 | 43 | fun pushback(size: Int) { 44 | if (currentStates.isNotEmpty()) { 45 | currentStates.addFirst(currentState) 46 | return 47 | } 48 | currentLexer.pushBack(size) 49 | doAdvance() 50 | 51 | if (currentStates.isEmpty()) { 52 | currentStates.addFirst(currentState) 53 | } 54 | } 55 | 56 | 57 | fun back(tokenSize: Int) { 58 | for (i in 0..= 6) { 87 | lastStates.removeLast() 88 | } 89 | } 90 | } 91 | 92 | internal data class LexerState( 93 | val text: CharSequence, 94 | val line: Int, 95 | val column: Int, 96 | val type: LuaTokenTypes, 97 | val length: Int 98 | ) -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/parser/ast/node/AbstractNode.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.parser.ast.node 2 | 3 | import io.github.dingyi222666.luaparser.parser.ast.visitor.ASTVisitor 4 | import kotlin.jvm.Transient 5 | import kotlin.properties.Delegates 6 | 7 | /** 8 | * @author: dingyi 9 | * @date: 2021/10/20 11:39 10 | * @description: 11 | **/ 12 | 13 | interface BaseASTNode { 14 | var parent: BaseASTNode 15 | var range: Range 16 | var bad: Boolean 17 | } 18 | 19 | interface StatementNode : BaseASTNode { 20 | fun clone(): StatementNode 21 | } 22 | 23 | interface ExpressionNode : BaseASTNode { 24 | companion object { 25 | val EMPTY = ExpressionNodeSupport() 26 | 27 | class ExpressionNodeSupport : ExpressionNode, ASTNode() { 28 | override fun accept(visitor: ASTVisitor, value: T) { 29 | visitor.visitExpressionNode(this, value) 30 | } 31 | 32 | override fun clone(): ExpressionNode { 33 | return EMPTY 34 | } 35 | 36 | override var bad = false 37 | } 38 | } 39 | 40 | fun clone(): ExpressionNode 41 | } 42 | 43 | 44 | abstract class ASTNode : BaseASTNode { 45 | @delegate:Transient 46 | override var parent: BaseASTNode by Delegates.notNull() 47 | 48 | override var range = Range.EMPTY 49 | 50 | abstract fun accept(visitor: ASTVisitor, value: T) 51 | 52 | abstract fun clone(): BaseASTNode 53 | 54 | override var bad = false 55 | } 56 | 57 | data class Range( 58 | var start: Position, 59 | var end: Position 60 | ) { 61 | companion object { 62 | val EMPTY = Range(Position.EMPTY, Position.EMPTY) 63 | } 64 | 65 | } 66 | 67 | data class Position( 68 | val line: Int, 69 | val column: Int 70 | ) : Comparable { 71 | override operator fun compareTo(other: Position): Int { 72 | if (other.line > line) { 73 | return other.line - line 74 | } 75 | if (other.line < line) { 76 | return other.line - line 77 | } 78 | if (other.column > column) { 79 | return 1 80 | } 81 | if (other.column < column) { 82 | return -1 83 | } 84 | return 0 85 | } 86 | 87 | companion object { 88 | val EMPTY = Position(1, 1) 89 | } 90 | 91 | override fun toString(): String { 92 | return "($line, $column)" 93 | } 94 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/parser/ast/node/blockNode.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.parser.ast.node 2 | 3 | import io.github.dingyi222666.luaparser.parser.ast.visitor.ASTVisitor 4 | 5 | /** 6 | * @author: dingyi 7 | * @date: 2021/10/7 10:11 8 | * @description: 9 | **/ 10 | class BlockNode : ASTNode() { 11 | 12 | val statements = mutableListOf() 13 | 14 | var returnStatement: ReturnStatement? = null 15 | 16 | fun addStatement(statement: StatementNode) { 17 | statements.add(statement) 18 | } 19 | 20 | override fun accept(visitor: ASTVisitor, value: T) { 21 | visitor.visitBlockNode(this, value) 22 | } 23 | 24 | override fun toString(): String { 25 | return "BlockNode(statements=$statements, returnStatement=$returnStatement)" 26 | } 27 | 28 | override fun clone(): BlockNode { 29 | val thisStatements = statements.map { it.clone() } 30 | return BlockNode().apply { 31 | statements.addAll(thisStatements) 32 | returnStatement = returnStatement?.clone() 33 | } 34 | } 35 | 36 | } 37 | 38 | /** 39 | * @author: dingyi 40 | * @date: 2021/10/7 10:10 41 | * @description: 42 | **/ 43 | class ChunkNode : ASTNode() { 44 | 45 | lateinit var body: BlockNode 46 | 47 | override fun accept(visitor: ASTVisitor, value: T) { 48 | visitor.visitChunkNode(this, value) 49 | } 50 | 51 | override fun toString(): String { 52 | return "ChunkNode(body=$body)" 53 | } 54 | 55 | override fun clone(): ChunkNode { 56 | return ChunkNode().apply { 57 | body = body.clone() 58 | } 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/parser/ast/node/expressionNode.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.parser.ast.node 2 | 3 | import io.github.dingyi222666.luaparser.parser.ast.visitor.ASTVisitor 4 | import io.github.dingyi222666.luaparser.util.parseLuaString 5 | import kotlin.jvm.Transient 6 | import kotlin.properties.Delegates 7 | 8 | 9 | /** 10 | * @author: dingyi 11 | * @date: 2021/10/7 10:48 12 | * @description: 13 | **/ 14 | open class Identifier(open var name: String = "") : ExpressionNode, ASTNode() { 15 | open var isLocal = false 16 | 17 | override fun toString(): String { 18 | return "Identifier(name='$name')" 19 | } 20 | 21 | override fun accept(visitor: ASTVisitor, value: T) { 22 | visitor.visitIdentifier(this, value) 23 | } 24 | 25 | override fun clone(): Identifier { 26 | return Identifier(name = name).also { 27 | it.isLocal = isLocal 28 | } 29 | } 30 | 31 | } 32 | 33 | /** 34 | * @author: dingyi 35 | * @date: 2024/9/19 18:04 36 | * @description: 37 | **/ 38 | class AttributeIdentifier( 39 | override var name: String = "", 40 | var attributeName: String? = null 41 | ) : Identifier(name) { 42 | override var isLocal = true 43 | override fun accept(visitor: ASTVisitor, value: T) { 44 | visitor.visitAttributeIdentifier(this, value) 45 | } 46 | 47 | override fun clone(): AttributeIdentifier { 48 | return AttributeIdentifier(name = name, attributeName = attributeName).also { 49 | it.isLocal = true 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * @author: dingyi 56 | * @date: 2021/10/7 10:38 57 | * @description: 58 | **/ 59 | class ConstantNode( 60 | var constantType: TYPE = TYPE.UNKNOWN, 61 | value: Any = 0 62 | ) : ExpressionNode, ASTNode() { 63 | 64 | 65 | private var _value: Any = 0 66 | 67 | @delegate:Transient 68 | var rawValue by Delegates.observable( 69 | initialValue = Any(), 70 | onChange = { _, _, newValue -> 71 | _value = switchValue(newValue) 72 | } 73 | ) 74 | 75 | private fun switchValue(newValue: Any): Any { 76 | return when (constantType) { 77 | TYPE.INTERGER -> { 78 | newValue.toString().toIntOrNull() 79 | ?: newValue 80 | } 81 | 82 | TYPE.FLOAT -> { 83 | newValue.toString().toFloatOrNull() ?: newValue 84 | } 85 | 86 | TYPE.BOOLEAN -> { 87 | newValue.toString() 88 | } 89 | 90 | TYPE.NIL -> "nil" 91 | 92 | else -> newValue 93 | } 94 | } 95 | 96 | 97 | init { 98 | this.rawValue = value 99 | } 100 | 101 | enum class TYPE { 102 | FLOAT, INTERGER, BOOLEAN, STRING, NIL, UNKNOWN 103 | } 104 | 105 | fun stringOf(): String { 106 | return parseLuaString(rawValue.toString()) 107 | } 108 | 109 | fun intOf(): Int { 110 | return _value as Int 111 | } 112 | 113 | fun floatOf(): Float { 114 | return _value as Float 115 | } 116 | 117 | fun booleanOf(): Boolean { 118 | return _value as Boolean 119 | } 120 | 121 | fun nilOf(): ConstantNode = NIL 122 | 123 | override fun toString(): String { 124 | return "ConstantsNode(type=$constantType, value=$_value)" 125 | } 126 | 127 | override fun clone(): ConstantNode = ConstantNode(constantType = this.constantType, value = this.rawValue) 128 | 129 | override fun accept(visitor: ASTVisitor, value: T) { 130 | visitor.visitConstantNode(this, value) 131 | } 132 | 133 | override fun equals(other: Any?): Boolean { 134 | if (this === other) return true 135 | if (other == null) return false 136 | if (this::class != other::class) return false 137 | 138 | other as ConstantNode 139 | 140 | if (constantType != other.constantType) return false 141 | if (_value != other._value) return false 142 | return rawValue == other.rawValue 143 | } 144 | 145 | override fun hashCode(): Int { 146 | var result = constantType.hashCode() 147 | result = 31 * result + _value.hashCode() 148 | result = 31 * result + rawValue.hashCode() 149 | return result 150 | } 151 | 152 | companion object { 153 | val NIL = ConstantNode(value = "nil", constantType = TYPE.NIL) 154 | } 155 | 156 | 157 | } 158 | 159 | /** 160 | * @author: dingyi 161 | * @date: 2021/10/9 15:00 162 | * @description: 163 | **/ 164 | open class CallExpression : ExpressionNode, ASTNode() { 165 | lateinit var base: ExpressionNode 166 | val arguments = mutableListOf() 167 | override fun toString(): String { 168 | return "CallExpression(base=$base, arguments=$arguments)" 169 | } 170 | 171 | override fun accept(visitor: ASTVisitor, value: T) { 172 | visitor.visitCallExpression(this, value) 173 | } 174 | 175 | override fun clone(): CallExpression { 176 | return CallExpression().also { 177 | it.base = base.clone() 178 | for (argument in arguments) { 179 | it.arguments.add(argument.clone()) 180 | } 181 | } 182 | } 183 | } 184 | 185 | class StringCallExpression : CallExpression() { 186 | 187 | override fun toString(): String { 188 | return "StringCallExpression(base=$base, arguments=$arguments)" 189 | } 190 | 191 | override fun accept(visitor: ASTVisitor, value: T) { 192 | visitor.visitStringCallExpression(this, value) 193 | } 194 | } 195 | 196 | 197 | class TableCallExpression : CallExpression() { 198 | 199 | override fun toString(): String { 200 | return "TableCallExpression(base=$base, arguments=$arguments)" 201 | } 202 | 203 | override fun accept(visitor: ASTVisitor, value: T) { 204 | visitor.visitTableCallExpression(this, value) 205 | } 206 | } 207 | 208 | 209 | class MemberExpression : ExpressionNode, ASTNode() { 210 | lateinit var identifier: Identifier 211 | var indexer: String = "." 212 | lateinit var base: ExpressionNode 213 | override fun toString(): String { 214 | return "MemberExpression(identifier=$identifier, indexer='$indexer', base=$base)" 215 | } 216 | 217 | override fun accept(visitor: ASTVisitor, value: T) { 218 | visitor.visitMemberExpression(this, value) 219 | } 220 | 221 | override fun clone(): MemberExpression { 222 | return MemberExpression().also { 223 | it.identifier = identifier.clone() 224 | it.base = base.clone() 225 | it.indexer = indexer 226 | } 227 | } 228 | } 229 | 230 | class IndexExpression : ExpressionNode, ASTNode() { 231 | 232 | lateinit var index: ExpressionNode 233 | lateinit var base: ExpressionNode 234 | 235 | override fun toString(): String { 236 | return "IndexExpression(index=$index, base=$base)" 237 | } 238 | 239 | override fun accept(visitor: ASTVisitor, value: T) { 240 | visitor.visitIndexExpression(this, value) 241 | } 242 | 243 | override fun clone(): IndexExpression { 244 | return IndexExpression().also { 245 | it.base = base.clone() 246 | it.index = index.clone() 247 | } 248 | } 249 | } 250 | 251 | class VarargLiteral : ExpressionNode, ASTNode() { 252 | 253 | override fun toString(): String { 254 | return "VarargLiteral()" 255 | } 256 | 257 | override fun accept(visitor: ASTVisitor, value: T) { 258 | visitor.visitVarargLiteral(this, value) 259 | } 260 | 261 | override fun clone(): VarargLiteral { 262 | return VarargLiteral() 263 | } 264 | } 265 | 266 | class UnaryExpression : ExpressionNode, ASTNode() { 267 | lateinit var operator: ExpressionOperator 268 | lateinit var arg: ExpressionNode 269 | override fun toString(): String { 270 | return "UnaryExpression(operator=$operator, arg=$arg)" 271 | } 272 | 273 | override fun accept(visitor: ASTVisitor, value: T) { 274 | visitor.visitUnaryExpression(this, value) 275 | } 276 | 277 | override fun clone(): UnaryExpression { 278 | return UnaryExpression().also { 279 | it.operator = operator 280 | it.arg = arg.clone() 281 | } 282 | } 283 | 284 | 285 | } 286 | 287 | class BinaryExpression : ExpressionNode, ASTNode() { 288 | var left /*by Delegates.notNull<*/: ExpressionNode? = null 289 | var right: ExpressionNode? = null 290 | lateinit var operator: ExpressionOperator/*? = null*/ 291 | override fun toString(): String { 292 | return "BinaryExpression(left=$left, right=$right, operator=$operator)" 293 | } 294 | 295 | override fun accept(visitor: ASTVisitor, value: T) { 296 | visitor.visitBinaryExpression(this, value) 297 | } 298 | 299 | override fun clone(): BinaryExpression { 300 | return BinaryExpression().also { 301 | it.operator = operator 302 | it.left = left?.clone() 303 | it.right = right?.clone() 304 | } 305 | } 306 | } 307 | 308 | class TableConstructorExpression : ExpressionNode, ASTNode() { 309 | val fields = mutableListOf() 310 | 311 | override fun toString(): String { 312 | return "TableConstructorExpression(fields=$fields)" 313 | } 314 | 315 | override fun accept(visitor: ASTVisitor, value: T) { 316 | visitor.visitTableConstructorExpression(this, value) 317 | } 318 | 319 | override fun clone(): TableConstructorExpression { 320 | return TableConstructorExpression().also { 321 | for (field in fields) { 322 | it.fields.add(field.clone()) 323 | } 324 | } 325 | } 326 | } 327 | 328 | class ArrayConstructorExpression : ExpressionNode, ASTNode() { 329 | val values = mutableListOf() 330 | 331 | override fun toString(): String { 332 | return "ArrayConstructorExpression(values=$values)" 333 | } 334 | 335 | override fun accept(visitor: ASTVisitor, value: T) { 336 | visitor.visitArrayConstructorExpression(this, value) 337 | } 338 | 339 | override fun clone(): ArrayConstructorExpression { 340 | return ArrayConstructorExpression().also { 341 | for (value in values) { 342 | it.values.add(value.clone()) 343 | } 344 | } 345 | } 346 | } 347 | 348 | enum class ExpressionOperator(val value: String) { 349 | NOT("not"), GETLEN("#"), BIT_TILDE("~"), MINUS("-"), 350 | ADD("+"), DIV("/"), OR("or"), MULT("*"), BIT_EXP("^"), 351 | LT("<"), BIT_LT("<<"), GT(">"), BIT_GT(">>"), BIT_OR("|"), 352 | BIT_AND("&"), CONCAT(".."), LE("<="), GE(">="), EQ("=="), 353 | NE("~="), DOUBLE_DIV("//"), MOD("%"), AND("and"); 354 | 355 | override fun toString(): String { 356 | return value 357 | } 358 | } 359 | 360 | class LambdaDeclaration : ExpressionNode, ASTNode() { 361 | val params = mutableListOf() 362 | lateinit var expression: ExpressionNode 363 | 364 | override fun toString(): String { 365 | return "LambdaDeclaration(params=$params, expression=$expression)" 366 | } 367 | 368 | override fun accept(visitor: ASTVisitor, value: T) { 369 | visitor.visitLambdaDeclaration(this, value) 370 | } 371 | 372 | override fun clone(): LambdaDeclaration { 373 | return LambdaDeclaration().also { declaration -> 374 | declaration.params.addAll(params.map { it.clone() }) 375 | 376 | declaration.expression = expression.clone() 377 | } 378 | } 379 | } 380 | 381 | 382 | class FunctionDeclaration : ExpressionNode, StatementNode, ASTNode() { 383 | var body: BlockNode? = null 384 | val params = mutableListOf() 385 | var identifier: ExpressionNode? = null 386 | var isLocal = false 387 | override fun toString(): String { 388 | return "FunctionDeclaration(body=$body, params=$params, identifier=$identifier, isLocal=$isLocal)" 389 | } 390 | 391 | override fun accept(visitor: ASTVisitor, value: T) { 392 | visitor.visitFunctionDeclaration(this, value) 393 | } 394 | 395 | override fun clone(): FunctionDeclaration { 396 | return FunctionDeclaration().also { declaration -> 397 | declaration.body = body?.clone() 398 | declaration.params.addAll(params.map { it.clone() }) 399 | declaration.identifier = identifier?.clone() 400 | declaration.isLocal = isLocal 401 | } 402 | } 403 | } 404 | 405 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/parser/ast/node/statementNode.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.parser.ast.node 2 | 3 | import io.github.dingyi222666.luaparser.parser.ast.visitor.ASTVisitor 4 | import kotlin.properties.Delegates 5 | 6 | /** 7 | * @author: dingyi 8 | * @date: 2021/10/7 10:23 9 | * @description: 10 | **/ 11 | class LocalStatement : StatementNode, ASTNode() { 12 | 13 | val variables: MutableList = mutableListOf() 14 | val init: MutableList = mutableListOf() 15 | override fun toString(): String { 16 | return "LocalStatement(variables=$variables, init=$init)" 17 | } 18 | 19 | override fun accept(visitor: ASTVisitor, value: T) { 20 | visitor.visitLocalStatement(this, value) 21 | } 22 | 23 | override fun clone(): LocalStatement { 24 | return LocalStatement().also { stat -> 25 | variables.forEach { 26 | stat.variables.add(it.clone()) 27 | } 28 | init.forEach { 29 | stat.init.add(it.clone()) 30 | } 31 | } 32 | } 33 | } 34 | 35 | 36 | class AssignmentStatement : StatementNode, ASTNode() { 37 | 38 | val variables: MutableList = mutableListOf() 39 | val init: MutableList = mutableListOf() 40 | override fun toString(): String { 41 | return "AssignmentStatement(variables=$variables, init=$init)" 42 | } 43 | 44 | override fun accept(visitor: ASTVisitor, value: T) { 45 | visitor.visitAssignmentStatement(this, value) 46 | } 47 | 48 | override fun clone(): AssignmentStatement { 49 | return AssignmentStatement().also { stat -> 50 | variables.forEach { 51 | stat.variables.add(it.clone()) 52 | } 53 | init.forEach { 54 | stat.init.add(it.clone()) 55 | } 56 | } 57 | } 58 | } 59 | 60 | 61 | class ForGenericStatement : StatementNode, ASTNode() { 62 | val variables: MutableList = mutableListOf() 63 | val iterators: MutableList = mutableListOf() 64 | lateinit var body: BlockNode 65 | 66 | override fun toString(): String { 67 | return "ForGenericStatement(variables=$variables, iterators=$iterators, body=$body)" 68 | } 69 | 70 | override fun accept(visitor: ASTVisitor, value: T) { 71 | visitor.visitForGenericStatement(this, value) 72 | } 73 | 74 | override fun clone(): ForGenericStatement { 75 | return ForGenericStatement().also { stat -> 76 | variables.forEach { 77 | stat.variables.add(it.clone()) 78 | } 79 | iterators.forEach { 80 | stat.iterators.add(it.clone()) 81 | } 82 | stat.body = body.clone() 83 | } 84 | } 85 | } 86 | 87 | 88 | class ForNumericStatement : StatementNode, ASTNode() { 89 | lateinit var variable: Identifier 90 | lateinit var start: ExpressionNode 91 | lateinit var end: ExpressionNode 92 | var step: ExpressionNode? = null 93 | lateinit var body: BlockNode 94 | override fun toString(): String { 95 | return "ForNumericStatement(variable=$variable, start=$start, end=$end, step=$step, body=$body)" 96 | } 97 | 98 | override fun accept(visitor: ASTVisitor, value: T) { 99 | visitor.visitForNumericStatement(this, value) 100 | } 101 | 102 | override fun clone(): ForNumericStatement { 103 | return ForNumericStatement().also { stat -> 104 | stat.variable = variable.clone() 105 | stat.start = start.clone() 106 | stat.end = end.clone() 107 | stat.step = step?.clone() 108 | stat.body = body.clone() 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * @author: dingyi 115 | * @date: 2021/10/9 14:58 116 | * @description: 117 | **/ 118 | class CallStatement : StatementNode, ASTNode() { 119 | lateinit var expression: CallExpression 120 | 121 | override fun toString(): String { 122 | return "CallStatement(expression=$expression)" 123 | } 124 | 125 | override fun accept(visitor: ASTVisitor, value: T) { 126 | visitor.visitCallStatement(this, value) 127 | } 128 | 129 | override fun clone(): CallStatement { 130 | return CallStatement().also { stat -> 131 | stat.expression = expression.clone() 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * @author: dingyi 138 | * @date: 2021/10/20 11:41 139 | * @description: 140 | **/ 141 | class WhileStatement : StatementNode, ASTNode() { 142 | lateinit var condition: ExpressionNode 143 | lateinit var body: BlockNode 144 | 145 | override fun toString(): String { 146 | return "WhileStatement(condition=$condition, body=$body)" 147 | } 148 | 149 | override fun accept(visitor: ASTVisitor, value: T) { 150 | visitor.visitWhileStatement(this, value) 151 | } 152 | 153 | override fun clone(): WhileStatement { 154 | return WhileStatement().also { stat -> 155 | stat.condition = condition.clone() 156 | stat.body = body.clone() 157 | } 158 | } 159 | } 160 | 161 | class RepeatStatement : StatementNode, ASTNode() { 162 | lateinit var condition: ExpressionNode 163 | lateinit var body: BlockNode 164 | override fun toString(): String { 165 | return "RepeatStatement(condition=$condition, body=$body)" 166 | } 167 | 168 | override fun accept(visitor: ASTVisitor, value: T) { 169 | visitor.visitRepeatStatement(this, value) 170 | } 171 | 172 | override fun clone(): RepeatStatement { 173 | return RepeatStatement().also { stat -> 174 | stat.condition = condition.clone() 175 | stat.body = body.clone() 176 | } 177 | } 178 | } 179 | 180 | 181 | class BreakStatement : StatementNode, ASTNode() { 182 | override fun toString(): String { 183 | return "BreakStatement()" 184 | } 185 | 186 | override fun accept(visitor: ASTVisitor, value: T) { 187 | visitor.visitBreakStatement(this, value) 188 | } 189 | 190 | override fun clone(): BreakStatement { 191 | return BreakStatement() 192 | } 193 | } 194 | 195 | class LabelStatement : StatementNode, ASTNode() { 196 | lateinit var identifier: Identifier 197 | override fun toString(): String { 198 | return "LabelStatement(identifier=$identifier)" 199 | } 200 | 201 | override fun accept(visitor: ASTVisitor, value: T) { 202 | visitor.visitLabelStatement(this, value) 203 | } 204 | 205 | override fun clone(): LabelStatement { 206 | return LabelStatement().also { stat -> 207 | stat.identifier = identifier.clone() 208 | } 209 | } 210 | } 211 | 212 | class GotoStatement : StatementNode, ASTNode() { 213 | lateinit var identifier: Identifier 214 | override fun toString(): String { 215 | return "GotoStatement(identifier=$identifier)" 216 | } 217 | 218 | override fun accept(visitor: ASTVisitor, value: T) { 219 | visitor.visitGotoStatement(this, value) 220 | } 221 | 222 | override fun clone(): GotoStatement { 223 | return GotoStatement().also { stat -> 224 | stat.identifier = identifier.clone() 225 | } 226 | } 227 | } 228 | 229 | 230 | class ContinueStatement : StatementNode, ASTNode() { 231 | override fun toString(): String { 232 | return "ContinueStatement()" 233 | } 234 | 235 | override fun accept(visitor: ASTVisitor, value: T) { 236 | visitor.visitContinueStatement(this, value) 237 | } 238 | 239 | override fun clone(): ContinueStatement { 240 | return ContinueStatement() 241 | } 242 | } 243 | 244 | class ReturnStatement : StatementNode, ASTNode() { 245 | val arguments = mutableListOf() 246 | 247 | override fun toString(): String { 248 | return "ReturnStatement(arguments=$arguments)" 249 | } 250 | 251 | override fun accept(visitor: ASTVisitor, value: T) { 252 | visitor.visitReturnStatement(this, value) 253 | } 254 | 255 | override fun clone(): ReturnStatement { 256 | return ReturnStatement().also { stat -> 257 | arguments.forEach { 258 | stat.arguments.add(it.clone()) 259 | } 260 | } 261 | } 262 | } 263 | 264 | class WhenStatement : StatementNode, ASTNode() { 265 | lateinit var condition: ExpressionNode 266 | lateinit var ifCause: StatementNode 267 | var elseCause: StatementNode? = null 268 | 269 | override fun toString(): String { 270 | return "WhenStatement(condition=$condition, ifCause=$ifCause, elseCause=$elseCause)" 271 | } 272 | 273 | override fun accept(visitor: ASTVisitor, value: T) { 274 | visitor.visitWhenStatement(this, value) 275 | } 276 | 277 | override fun clone(): WhenStatement { 278 | return WhenStatement().also { stat -> 279 | stat.condition = condition.clone() 280 | stat.ifCause = ifCause.clone() 281 | stat.elseCause = elseCause?.clone() 282 | } 283 | } 284 | } 285 | 286 | class SwitchStatement : StatementNode, ASTNode() { 287 | lateinit var condition: ExpressionNode 288 | val causes = mutableListOf() 289 | 290 | override fun toString(): String { 291 | return "SwitchStatement(condition=$condition, causes=$causes)" 292 | } 293 | 294 | override fun accept(visitor: ASTVisitor, value: T) { 295 | visitor.visitSwitchStatement(this, value) 296 | } 297 | 298 | 299 | override fun clone(): SwitchStatement { 300 | return SwitchStatement().also { stat -> 301 | stat.condition = condition.clone() 302 | stat.causes.forEach { 303 | stat.causes.add(it.clone()) 304 | } 305 | } 306 | } 307 | } 308 | 309 | abstract class AbsSwitchCause : StatementNode, ASTNode() { 310 | abstract override fun clone(): AbsSwitchCause 311 | } 312 | 313 | class CaseCause : AbsSwitchCause() { 314 | val conditions = mutableListOf() 315 | lateinit var body: BlockNode 316 | 317 | override fun toString(): String { 318 | return "CaseCause(conditions=$conditions, body=$body)" 319 | } 320 | 321 | override fun accept(visitor: ASTVisitor, value: T) { 322 | visitor.visitCaseCause(this, value) 323 | } 324 | 325 | override fun clone(): CaseCause { 326 | return CaseCause().also { stat -> 327 | conditions.forEach { 328 | stat.conditions.add(it.clone()) 329 | } 330 | stat.body = body.clone() 331 | } 332 | } 333 | } 334 | 335 | class DefaultCause : AbsSwitchCause() { 336 | lateinit var body: BlockNode 337 | 338 | override fun toString(): String { 339 | return "DefaultCause(body=$body)" 340 | } 341 | 342 | override fun accept(visitor: ASTVisitor, value: T) { 343 | visitor.visitDefaultCause(this, value) 344 | } 345 | 346 | override fun clone(): DefaultCause { 347 | return DefaultCause().also { stat -> 348 | stat.body = body.clone() 349 | } 350 | } 351 | } 352 | 353 | open class IfClause : StatementNode, ASTNode() { 354 | lateinit var condition: ExpressionNode 355 | lateinit var body: BlockNode 356 | 357 | override fun toString(): String { 358 | return "IfClause(condition=$condition, body=$body)" 359 | } 360 | 361 | override fun accept(visitor: ASTVisitor, value: T) { 362 | visitor.visitIfClause(this, value) 363 | } 364 | 365 | override fun clone(): IfClause { 366 | return IfClause().also { stat -> 367 | stat.condition = condition.clone() 368 | stat.body = body.clone() 369 | } 370 | } 371 | } 372 | 373 | 374 | class ElseIfClause : IfClause() { 375 | override fun toString(): String { 376 | return "ElseIfClause(condition=$condition, body=$body)" 377 | } 378 | 379 | override fun accept(visitor: ASTVisitor, value: T) { 380 | visitor.visitElseIfClause(this, value) 381 | } 382 | } 383 | 384 | class ElseClause : IfClause() { 385 | override fun toString(): String { 386 | return "ElseClause(body=$body)" 387 | } 388 | 389 | override fun accept(visitor: ASTVisitor, value: T) { 390 | visitor.visitElseClause(this, value) 391 | } 392 | } 393 | 394 | 395 | 396 | open class TableKey : ExpressionNode, ASTNode() { 397 | lateinit var key: ExpressionNode 398 | lateinit var value: ExpressionNode 399 | 400 | override fun toString(): String { 401 | return "TableKey(key=$key, value=$value)" 402 | } 403 | 404 | override fun accept(visitor: ASTVisitor, value: T) { 405 | visitor.visitTableKey(this, value) 406 | } 407 | 408 | override fun clone(): TableKey { 409 | return TableKey().also { 410 | it.key = key.clone() 411 | it.value = value.clone() 412 | } 413 | } 414 | } 415 | 416 | open class TableKeyString : TableKey() { 417 | override fun toString(): String { 418 | return "TableKeyString(key=$key, value=$value)" 419 | } 420 | 421 | override fun accept(visitor: ASTVisitor, value: T) { 422 | visitor.visitTableKeyString(this, value) 423 | } 424 | } 425 | 426 | 427 | class IfStatement : StatementNode, ASTNode() { 428 | val causes = mutableListOf() 429 | 430 | override fun toString(): String { 431 | return "IfStatement(causes=$causes)" 432 | } 433 | 434 | override fun accept(visitor: ASTVisitor, value: T) { 435 | visitor.visitIfStatement(this, value) 436 | } 437 | 438 | override fun clone(): IfStatement { 439 | return IfStatement().also { stat -> 440 | stat.causes.forEach { 441 | stat.causes.add(it.clone()) 442 | } 443 | } 444 | } 445 | } 446 | 447 | /** 448 | * @author: dingyi 449 | * @date: 2021/10/8 20:08 450 | * @description: 451 | **/ 452 | class DoStatement : StatementNode, ASTNode() { 453 | var body by Delegates.notNull() 454 | override fun toString(): String { 455 | return "DoStatement(body=$body)" 456 | } 457 | 458 | override fun accept(visitor: ASTVisitor, value: T) { 459 | visitor.visitDoStatement(this, value) 460 | } 461 | 462 | override fun clone(): DoStatement { 463 | return DoStatement().also { stat -> 464 | stat.body = body.clone() 465 | } 466 | } 467 | } 468 | 469 | class CommentStatement : StatementNode, ASTNode() { 470 | var comment by Delegates.notNull() 471 | 472 | var isDocComment = false 473 | 474 | override fun toString(): String { 475 | return "CommentStatement(comment=$comment)" 476 | } 477 | 478 | override fun accept(visitor: ASTVisitor, value: T) { 479 | visitor.visitCommentStatement(this, value) 480 | } 481 | 482 | override fun clone(): CommentStatement { 483 | return CommentStatement().also { stat -> 484 | stat.comment = comment 485 | } 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/parser/version.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.parser 2 | 3 | enum class LuaVersion(val value: Int) { 4 | LUA_5_3(530), 5 | LUA_5_4(540), 6 | ANDROLUA_5_3(531), 7 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/semantic/SemanticAnalyzer.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.semantic 2 | 3 | import io.github.dingyi222666.luaparser.parser.ast.node.* 4 | import io.github.dingyi222666.luaparser.parser.ast.visitor.ASTVisitor 5 | import io.github.dingyi222666.luaparser.semantic.comment.ClassTag 6 | import io.github.dingyi222666.luaparser.semantic.types.* 7 | import io.github.dingyi222666.luaparser.semantic.symbol.SymbolTable 8 | import io.github.dingyi222666.luaparser.semantic.comment.CommentProcessor 9 | import io.github.dingyi222666.luaparser.semantic.comment.FieldTag 10 | import io.github.dingyi222666.luaparser.semantic.comment.GenericTag 11 | import io.github.dingyi222666.luaparser.semantic.comment.MethodTag 12 | import io.github.dingyi222666.luaparser.semantic.comment.ParamTag 13 | import io.github.dingyi222666.luaparser.semantic.comment.ReturnTag 14 | import io.github.dingyi222666.luaparser.semantic.comment.TypeTag 15 | import io.github.dingyi222666.luaparser.semantic.symbol.Symbol 16 | 17 | class SemanticAnalyzer : ASTVisitor { 18 | private val typeInferer = TypeInferer() 19 | private val typeAnnotationParser = TypeAnnotationParser() 20 | private val commentProcessor = CommentProcessor(typeAnnotationParser) 21 | private val diagnostics = mutableListOf() 22 | private var currentSymbolTable: SymbolTable = SymbolTable() 23 | 24 | private val globalSymbols = mutableMapOf() 25 | 26 | init { 27 | defineGlobalSymbol("print", FunctionType( 28 | parameters = listOf(ParameterType("...", PrimitiveType.ANY, vararg = true)), 29 | returnType = PrimitiveType.NIL 30 | )) 31 | } 32 | 33 | private fun defineGlobalSymbol(name: String, type: Type) { 34 | globalSymbols[name] = Symbol(name, type, Symbol.Kind.VARIABLE) 35 | } 36 | 37 | fun analyze(ast: ChunkNode): AnalysisResult { 38 | diagnostics.clear() 39 | currentSymbolTable = SymbolTable() 40 | 41 | 42 | visitChunkNode(ast, TypeContext()) 43 | 44 | return AnalysisResult( 45 | diagnostics = diagnostics, 46 | symbolTable = currentSymbolTable, 47 | globalSymbols = globalSymbols.toMap() 48 | ) 49 | } 50 | 51 | override fun visitBlockNode(node: BlockNode, context: TypeContext) { 52 | val previousTable = currentSymbolTable 53 | currentSymbolTable = currentSymbolTable.createChild(node.range) 54 | 55 | commentProcessor.processComments(node.statements) 56 | 57 | processClassDefinitions(node.statements) 58 | 59 | super.visitBlockNode(node, context) 60 | 61 | currentSymbolTable = previousTable 62 | } 63 | 64 | 65 | private fun processClassDefinitions(statements: List) { 66 | // 第一遍:收集所有类定义 67 | statements.forEach { stmt -> 68 | if (stmt is LocalStatement || stmt is AssignmentStatement) { 69 | val comments = commentProcessor.getAdjacentComments(stmt) 70 | val classTag = comments.findLast { comment -> 71 | comment.isDocComment && comment.comment.contains("@class") 72 | }?.let { comment -> 73 | commentProcessor.parseDocComment(comment, comment.range.start.line) 74 | .tags.firstOrNull { it is ClassTag } as? ClassTag 75 | } 76 | 77 | if (classTag != null) { 78 | val fields = mutableMapOf() 79 | val methods = mutableMapOf() 80 | 81 | // 处理类的字段和基本方法 82 | comments.forEach { comment -> 83 | if (comment.isDocComment) { 84 | val docComment = commentProcessor.parseDocComment( 85 | comment, 86 | comment.range.start.line 87 | ) 88 | 89 | docComment.tags.forEach { tag -> 90 | when (tag) { 91 | is FieldTag -> fields[tag.name] = tag.type 92 | is MethodTag -> methods[tag.methodName] = tag.type 93 | is ClassTag -> TODO() 94 | is GenericTag -> TODO() 95 | is ParamTag -> TODO() 96 | is ReturnTag -> TODO() 97 | is TypeTag -> TODO() 98 | } 99 | } 100 | } 101 | } 102 | 103 | // 添加动态声明的方法 104 | methods.putAll(commentProcessor.getClassMethods(classTag.name)) 105 | 106 | val classType = typeAnnotationParser.defineClass( 107 | name = classTag.name, 108 | fields = fields, 109 | methods = methods 110 | ) 111 | 112 | if (stmt is LocalStatement) { 113 | stmt.init.forEach { id -> 114 | currentSymbolTable.define( 115 | id.name, 116 | classType, 117 | Symbol.Kind.CLASS, 118 | id.range 119 | ) 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | // 第二遍:处理方法实现和类型检查 127 | statements.forEach { stmt -> 128 | if (stmt is AssignmentStatement) { 129 | val target = stmt.init.firstOrNull() 130 | if (target is MemberExpression) { 131 | val baseType = typeInferer.inferType(target.base, TypeContext()) 132 | if (baseType is ClassType) { 133 | // 检查是否是方法赋值 134 | val methodName = target.identifier.name 135 | val methodType = baseType.methods[methodName] 136 | if (methodType != null) { 137 | // 验证方法实现的类型 138 | val implementation = stmt.variables.firstOrNull() 139 | if (implementation != null) { 140 | val implType = typeInferer.inferType(implementation, TypeContext()) 141 | if (!methodType.isAssignableFrom(implType)) { 142 | diagnostics.add(Diagnostic( 143 | range = stmt.range, 144 | message = "Method implementation type mismatch: expected ${methodType.name}, got ${implType.name}", 145 | severity = Diagnostic.Severity.ERROR 146 | )) 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | override fun visitAttributeIdentifier( 157 | identifier: AttributeIdentifier, 158 | value: TypeContext 159 | ) { 160 | TODO("Not yet implemented") 161 | } 162 | 163 | override fun visitLocalStatement(node: LocalStatement, context: TypeContext) { 164 | node.init.forEachIndexed { index, identifier -> 165 | val valueExpr = node.variables.getOrNull(index) 166 | 167 | // 获取声明的类型注释 168 | val declaredType = commentProcessor.findTypeAnnotation(node) ?: run { 169 | if (valueExpr != null) { 170 | when (valueExpr) { 171 | is FunctionDeclaration -> { 172 | // 对于函数声明,获取其文档注释 173 | val docComment = commentProcessor.getFunctionDocComment(valueExpr) 174 | if (docComment != null) { 175 | try { 176 | val params = mutableListOf() 177 | var returnType: Type = PrimitiveType.NIL 178 | 179 | docComment.tags.forEach { tag -> 180 | when (tag) { 181 | is ParamTag -> { 182 | params.add(ParameterType(tag.name, tag.type)) 183 | } 184 | is ReturnTag -> returnType = tag.type 185 | else -> {} // 忽略其他标签 186 | } 187 | } 188 | 189 | FunctionType(params, returnType) 190 | } catch (e: Exception) { 191 | typeInferer.inferType(valueExpr, context) 192 | } 193 | } else { 194 | typeInferer.inferType(valueExpr, context) 195 | } 196 | } 197 | else -> typeInferer.inferType(valueExpr, context) 198 | } 199 | } else { 200 | PrimitiveType.ANY 201 | } 202 | } 203 | 204 | currentSymbolTable.define( 205 | identifier.name, 206 | declaredType, 207 | Symbol.Kind.LOCAL, 208 | identifier.range 209 | ) 210 | 211 | context.defineType(identifier.name, declaredType) 212 | 213 | if (valueExpr != null) { 214 | val inferredType = typeInferer.inferType(valueExpr, context) 215 | diagnostics.addAll(typeInferer.getDiagnostics()) 216 | 217 | if (!declaredType.isAssignableFrom(inferredType)) { 218 | diagnostics.add(Diagnostic( 219 | range = node.range, 220 | message = "Type '${inferredType.name}' is not assignable to type '${declaredType.name}'", 221 | severity = Diagnostic.Severity.ERROR 222 | )) 223 | } 224 | } 225 | } 226 | } 227 | 228 | override fun visitFunctionDeclaration(node: FunctionDeclaration, context: TypeContext) { 229 | val functionScope = currentSymbolTable.createChild(node.range) 230 | val previousTable = currentSymbolTable 231 | currentSymbolTable = functionScope 232 | 233 | // 获取函数的文档注释 234 | val docComment = commentProcessor.getFunctionDocComment(node) 235 | 236 | // 获取推导的类型 237 | val inferredType = typeInferer.inferType(node, context) as? FunctionType 238 | 239 | // 处理函数类型 240 | val functionType = if (docComment != null) { 241 | try { 242 | val params = mutableListOf() 243 | var returnType: Type = inferredType?.returnType ?: PrimitiveType.NIL 244 | val genericParams = mutableListOf() 245 | 246 | // 创建参数映射 247 | val paramMap = node.params.associateBy { it.name } 248 | 249 | // 处理参数和返回类型标注 250 | docComment.tags.forEach { tag -> 251 | when (tag) { 252 | is ParamTag -> { 253 | println(tag) 254 | // 只处理存在的参数 255 | if (paramMap.containsKey(tag.name)) { 256 | params.add(ParameterType(tag.name, tag.type)) 257 | } 258 | } 259 | is GenericTag -> { 260 | genericParams.add(tag) 261 | context.defineType(tag.name, GenericType(tag.name, emptyList())) 262 | } 263 | 264 | is ReturnTag -> returnType = tag.type 265 | else -> {} // 忽略其他标签 266 | } 267 | } 268 | 269 | // 如果有参数没有类型注释,使用推导的类型或 ANY 270 | node.params.forEach { param -> 271 | if (!params.any { it.name == param.name }) { 272 | val inferredParamType = inferredType?.parameters?.find { it.name == param.name }?.type 273 | params.add(ParameterType(param.name, inferredParamType ?: PrimitiveType.ANY)) 274 | } 275 | } 276 | 277 | FunctionType(params, returnType) 278 | } catch (e: Exception) { 279 | inferredType ?: FunctionType( 280 | parameters = node.params.map { ParameterType(it.name, PrimitiveType.ANY) }, 281 | returnType = PrimitiveType.ANY 282 | ) 283 | } 284 | } else { 285 | inferredType ?: FunctionType( 286 | parameters = node.params.map { ParameterType(it.name, PrimitiveType.ANY) }, 287 | returnType = PrimitiveType.ANY 288 | ) 289 | } 290 | 291 | // 处理函数标识符 292 | when (val identifier = node.identifier) { 293 | is Identifier -> { 294 | if (!node.isLocal) { 295 | // 全局函数 296 | defineGlobalSymbol(identifier.name, functionType) 297 | context.defineType(identifier.name, functionType) 298 | } else { 299 | // 局部函数 300 | currentSymbolTable.define( 301 | identifier.name, 302 | functionType, 303 | Symbol.Kind.FUNCTION, 304 | identifier.range 305 | ) 306 | context.defineType(identifier.name, functionType) 307 | } 308 | } 309 | is MemberExpression -> { 310 | // 处理方法声明 311 | val baseType = typeInferer.inferType(identifier.base, context) 312 | if (baseType is ClassType) { 313 | val methodName = identifier.identifier.name 314 | // 更新类的方法 315 | val updatedMethods = baseType.methods + (methodName to functionType) 316 | typeAnnotationParser.defineClass( 317 | baseType.name, 318 | baseType.fields, 319 | updatedMethods, 320 | baseType.parent?.name 321 | ) 322 | } 323 | } 324 | } 325 | 326 | // 处理参数 327 | node.params.forEach { param -> 328 | val paramType = functionType.parameters.find { it.name == param.name }?.type ?: PrimitiveType.ANY 329 | currentSymbolTable.define( 330 | param.name, 331 | paramType, 332 | Symbol.Kind.PARAMETER, 333 | param.range 334 | ) 335 | context.defineType(param.name, paramType) 336 | } 337 | 338 | // 处理函数体 339 | node.body?.let { visitBlockNode(it, context) } 340 | 341 | currentSymbolTable = previousTable 342 | } 343 | 344 | override fun visitAssignmentStatement(node: AssignmentStatement, context: TypeContext) { 345 | node.init.forEachIndexed { index, target -> 346 | val valueExpr = node.variables.getOrNull(index) 347 | if (valueExpr != null) { 348 | val targetType = when (target) { 349 | is Identifier -> { 350 | if (!target.isLocal) { 351 | val inferredType = typeInferer.inferType(valueExpr, context) 352 | val declaredType = commentProcessor.findTypeAnnotation(node) ?: inferredType 353 | 354 | defineGlobalSymbol(target.name, declaredType) 355 | 356 | if (!declaredType.isAssignableFrom(inferredType)) { 357 | diagnostics.add(Diagnostic( 358 | range = node.range, 359 | message = "Global variable '${target.name}' of type '${declaredType.name}' is not assignable from type '${inferredType.name}'", 360 | severity = Diagnostic.Severity.ERROR 361 | )) 362 | } 363 | 364 | declaredType 365 | } else { 366 | val inferredType = typeInferer.inferType(valueExpr, context) 367 | currentSymbolTable.define( 368 | target.name, 369 | inferredType, 370 | Symbol.Kind.VARIABLE, 371 | target.range 372 | ) 373 | context.defineType(target.name, inferredType) 374 | inferredType 375 | } 376 | } 377 | is MemberExpression -> { 378 | val baseType = typeInferer.inferType(target.base, context) 379 | when (baseType) { 380 | is TableType -> baseType.fields[target.identifier.name] ?: PrimitiveType.ANY 381 | else -> PrimitiveType.ANY 382 | } 383 | } 384 | else -> PrimitiveType.ANY 385 | } 386 | 387 | val valueType = typeInferer.inferType(valueExpr, context) 388 | if (!targetType.isAssignableFrom(valueType)) { 389 | diagnostics.add(Diagnostic( 390 | range = node.range, 391 | message = "Type '${valueType.name}' is not assignable to type '${targetType.name}'", 392 | severity = Diagnostic.Severity.ERROR 393 | )) 394 | } 395 | } 396 | } 397 | } 398 | 399 | override fun visitMemberExpression(node: MemberExpression, context: TypeContext) { 400 | val baseType = typeInferer.inferType(node.base, context) 401 | when (baseType) { 402 | is TableType -> { 403 | val fieldType = baseType.fields[node.identifier.name] 404 | print(fieldType) 405 | if (fieldType != null) { 406 | val fullPath = buildMemberPath(node) 407 | context.defineType(fullPath, fieldType) 408 | 409 | context.defineType(node.toString(), fieldType) 410 | } 411 | } 412 | 413 | is FunctionType -> TODO() 414 | NeverType -> TODO() 415 | is PrimitiveType -> TODO() 416 | is UnionType -> TODO() 417 | is ArrayType -> TODO() 418 | is CustomType -> TODO() 419 | is GenericType -> TODO() 420 | is VarArgType -> TODO() 421 | is ClassType -> { 422 | val member = if (node.indexer == ":") { 423 | baseType.getAllMethods()[node.identifier.name] 424 | } else { 425 | baseType.getAllFields()[node.identifier.name] 426 | } 427 | 428 | if (member != null) { 429 | context.defineType(buildMemberPath(node), member) 430 | context.defineType(node.toString(), member) 431 | } else { 432 | diagnostics.add(Diagnostic( 433 | range = node.range, 434 | message = "Member '${node.identifier.name}' not found in class '${baseType.name}'", 435 | severity = Diagnostic.Severity.ERROR 436 | )) 437 | } 438 | } 439 | } 440 | } 441 | 442 | private fun buildMemberPath(node: MemberExpression): String { 443 | return when (val base = node.base) { 444 | is MemberExpression -> "${buildMemberPath(base)}.${node.identifier.name}" 445 | else -> "${base}.${node.identifier.name}" 446 | } 447 | } 448 | 449 | override fun visitCommentStatement(commentStatement: CommentStatement, value: TypeContext) { 450 | super.visitCommentStatement(commentStatement, value) 451 | } 452 | 453 | private fun resolveSymbol(name: String, position: Position): Symbol? { 454 | return currentSymbolTable.resolveAtPosition(name, position) 455 | ?: globalSymbols[name] 456 | } 457 | 458 | 459 | private fun parseType(typeStr: String?): Type { 460 | if (typeStr == null) return PrimitiveType.ANY 461 | 462 | return typeAnnotationParser.parse(typeStr) 463 | } 464 | } 465 | 466 | data class AnalysisResult( 467 | val diagnostics: List, 468 | val symbolTable: SymbolTable, 469 | val globalSymbols: Map 470 | ) 471 | 472 | data class Diagnostic( 473 | val range: Range, 474 | val message: String, 475 | val severity: Severity = Severity.ERROR 476 | ) { 477 | enum class Severity { 478 | ERROR, 479 | WARNING, 480 | INFO 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/semantic/comment/CommentProcessor.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.semantic.comment 2 | 3 | import io.github.dingyi222666.luaparser.parser.ast.node.* 4 | import io.github.dingyi222666.luaparser.semantic.types.* 5 | import io.github.dingyi222666.luaparser.semantic.types.Type 6 | 7 | class CommentProcessor( 8 | private val typeAnnotationParser: TypeAnnotationParser 9 | ) { 10 | // 存储所有注释节点,按行号索引 11 | private val commentsByLine = mutableMapOf>() 12 | 13 | // 存储已解析的文档注释 14 | private val docComments = mutableMapOf() 15 | 16 | // 存储类的方法声明 17 | private val classMethods = mutableMapOf>() 18 | 19 | // 添加新方法用于注册类方法 20 | fun addClassMethod(className: String, methodName: String, methodType: FunctionType) { 21 | classMethods.getOrPut(className) { mutableMapOf() }[methodName] = methodType 22 | } 23 | 24 | // 获取类的所有方法声明 25 | fun getClassMethods(className: String): Map { 26 | return classMethods[className] ?: emptyMap() 27 | } 28 | 29 | // 处理并索引所有注释 30 | fun processComments(statements: List) { 31 | commentsByLine.clear() 32 | docComments.clear() 33 | 34 | // 收集所有注释并按行号索引 35 | statements.forEach { stmt -> 36 | if (stmt is CommentStatement) { 37 | val line = stmt.range.end.line 38 | commentsByLine.getOrPut(line) { mutableListOf() }.add(stmt) 39 | 40 | // 如果是文档注释,解析它 41 | if (stmt.isDocComment) { 42 | val docComment = parseDocComment(stmt, line) 43 | 44 | // 处理类定义 45 | docComment.tags.filterIsInstance().forEach { classTag -> 46 | // 收集类的字段和方法 47 | val fields = mutableMapOf() 48 | val methods = mutableMapOf() 49 | 50 | docComment.tags.forEach { tag -> 51 | when (tag) { 52 | is FieldTag -> fields[tag.name] = tag.type 53 | is MethodTag -> methods[tag.methodName] = tag.type 54 | is ClassTag -> TODO() 55 | is GenericTag -> TODO() 56 | is ParamTag -> TODO() 57 | is ReturnTag -> TODO() 58 | is TypeTag -> TODO() 59 | } 60 | } 61 | 62 | // 注册类定义 63 | typeAnnotationParser.defineClass( 64 | name = classTag.name, 65 | fields = fields, 66 | methods = methods 67 | ) 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | internal fun parseDocComment(comment: CommentStatement, line: Int): DocComment { 75 | val lines = comment.comment.lines() 76 | val description = StringBuilder() 77 | val tags = mutableListOf() 78 | 79 | var currentTag: DocTag? = null 80 | var isInDescription = true 81 | for (lineText in lines) { 82 | val trimmed = lineText.trim().removePrefix("---").trim() 83 | 84 | if (trimmed.startsWith("@")) { 85 | isInDescription = false 86 | currentTag = parseDocTag(trimmed) 87 | if (currentTag != null) { 88 | tags.add(currentTag) 89 | } 90 | } else if (currentTag != null) { 91 | // 添加到当前标签的描述中 92 | currentTag.description += "\n$trimmed" 93 | } else if (isInDescription && trimmed.isNotEmpty()) { 94 | // 添加到主描述中 95 | description.append(trimmed).append("\n") 96 | } 97 | } 98 | 99 | val comment = DocComment( 100 | description = description.toString().trim(), 101 | tags = tags 102 | ) 103 | 104 | docComments[line] = comment 105 | 106 | return comment 107 | } 108 | 109 | private fun parseDocTag(text: String): DocTag? { 110 | val parts = text.removePrefix("@").split(Regex("\\s+"), 2) 111 | return when (parts[0]) { 112 | "param" -> { 113 | val paramParts = parts.getOrNull(1)?.split(Regex("\\s+"), 2) 114 | val paramName = paramParts?.getOrNull(0) ?: "" 115 | val paramTypeStr = paramParts?.getOrNull(1) 116 | 117 | // 处理参数类型,保留完整的类型字符串 118 | val paramType = if (paramTypeStr != null) { 119 | parseType(paramTypeStr) 120 | } else PrimitiveType.ANY 121 | 122 | ParamTag( 123 | name = paramName, 124 | type = paramType, 125 | description = paramParts?.getOrNull(1) ?: "" 126 | ) 127 | } 128 | 129 | "method" -> { 130 | // 解析方法声明 @method Class.methodName(param1: type1, param2: type2): returnType 131 | val methodInfo = parts.getOrNull(1) ?: return null 132 | val methodMatch = Regex("""(\w+)\.(\w+)\((.*?)\)(?:\s*:\s*(.+))?"""").find(methodInfo) 133 | if (methodMatch != null) { 134 | val (className, methodName, params, returnTypeStr) = methodMatch.destructured 135 | 136 | val parameters = if (params.isNotBlank()) { 137 | params.split(",").map { param -> 138 | val (name, type) = param.trim().split(":").map { it.trim() } 139 | ParameterType(name, parseType(type)) 140 | } 141 | } else emptyList() 142 | 143 | val returnType = if (returnTypeStr.isNotBlank()) { 144 | parseType(returnTypeStr) 145 | } else PrimitiveType.NIL 146 | 147 | val methodType = FunctionType(parameters, returnType) 148 | addClassMethod(className, methodName, methodType) 149 | 150 | return MethodTag( 151 | className = className, 152 | name = methodName, 153 | type = methodType, 154 | description = "" 155 | ) 156 | } 157 | null 158 | } 159 | 160 | "return" -> { 161 | val returnTypeStr = parts.getOrNull(1) 162 | if (returnTypeStr != null) { 163 | // 保留完整的返回类型字符串,不要分割 164 | ReturnTag( 165 | type = parseType(returnTypeStr), 166 | description = returnTypeStr 167 | ) 168 | } else { 169 | ReturnTag( 170 | type = PrimitiveType.NIL, 171 | description = "" 172 | ) 173 | } 174 | } 175 | 176 | "generic" -> { 177 | val genericParts = parts.getOrNull(1)?.split(Regex("\\s+"), 2) 178 | GenericTag( 179 | name = genericParts?.getOrNull(0) ?: "", 180 | constraint = genericParts?.getOrNull(1)?.let { parseType(it) }, 181 | description = "" 182 | ) 183 | } 184 | 185 | "type" -> { 186 | val typeStr = parts.getOrNull(1) 187 | if (typeStr != null) { 188 | val type = parseType(typeStr) 189 | TypeTag( 190 | type = type, 191 | description = typeStr 192 | ) 193 | } else { 194 | TypeTag( 195 | type = PrimitiveType.ANY, 196 | description = "" 197 | ) 198 | } 199 | } 200 | 201 | "class" -> ClassTag( 202 | name = parts.getOrNull(1)?.split(Regex("\\s+"))?.get(0) ?: "", 203 | description = parts.getOrNull(1) ?: "" 204 | ) 205 | 206 | "field" -> FieldTag( 207 | name = parts.getOrNull(1)?.split(Regex("\\s+"))?.get(0) ?: "", 208 | type = parseType(parts.getOrNull(1)?.split(Regex("\\s+"))?.getOrNull(1)), 209 | description = parts.getOrNull(1)?.split(Regex("\\s+"), 3)?.getOrNull(2) ?: "" 210 | ) 211 | 212 | else -> null 213 | } 214 | } 215 | 216 | private fun parseType(typeStr: String?): Type { 217 | if (typeStr == null) return PrimitiveType.ANY 218 | 219 | val trimmed = typeStr.trim() 220 | 221 | // 保持完整的类型字符串传递给 typeAnnotationParser 222 | return typeAnnotationParser.parse(trimmed) 223 | } 224 | 225 | // 获取函数的文档注释 226 | fun getFunctionDocComment(node: FunctionDeclaration): DocComment? { 227 | val nodeLine = node.range.start.line 228 | return docComments[nodeLine] 229 | } 230 | 231 | // 获取节点的类型注释 232 | fun findTypeAnnotation(node: BaseASTNode): Type? { 233 | val nodeLine = node.range.start.line 234 | var currentLine = nodeLine 235 | 236 | while (currentLine > 0) { 237 | // 先检查是否有文档注释 238 | val docComment = docComments[currentLine] 239 | if (docComment != null) { 240 | // 1. 检查是否有直接的类型标签 241 | val typeTag = docComment.tags.firstOrNull { it is TypeTag } as? TypeTag 242 | if (typeTag != null) { 243 | return typeTag.type 244 | } 245 | 246 | // 2. 如果是函数声明,尝试从参数和返回类型标签构建函数类型 247 | if (node is FunctionDeclaration) { 248 | val paramTags = docComment.tags.filterIsInstance() 249 | val returnTag = docComment.tags.firstOrNull { it is ReturnTag } as? ReturnTag 250 | 251 | if (paramTags.isNotEmpty() || returnTag != null) { 252 | val params = paramTags.map { ParameterType(it.name, it.type) } 253 | val returnType = returnTag?.type ?: PrimitiveType.NIL 254 | return FunctionType(params, returnType) 255 | } 256 | } 257 | 258 | break 259 | } 260 | 261 | // 然后检查是否有类型注释 262 | val comments = commentsByLine[currentLine] 263 | if (comments != null) { 264 | val typeComment = comments.findLast { comment -> 265 | comment.comment.trim().startsWith("---@type") 266 | } 267 | 268 | if (typeComment != null) { 269 | val type = typeComment.comment.trim() 270 | .removePrefix("---@type") 271 | .trim() 272 | return typeAnnotationParser.parse(type) 273 | } 274 | 275 | // 如果有非类型注释,停止查找 276 | if (comments.any { !it.comment.trim().startsWith("---@") }) { 277 | break 278 | } 279 | } 280 | 281 | // 如果这一行没有注释,且与节点行相差超过1行,停止查找 282 | if (comments == null && nodeLine - currentLine > 1) { 283 | break 284 | } 285 | 286 | currentLine-- 287 | } 288 | 289 | return null 290 | } 291 | 292 | // 获取节点紧邻的所有注释 293 | fun getAdjacentComments(node: BaseASTNode): List { 294 | val nodeLine = node.range.start.line 295 | val result = mutableListOf() 296 | 297 | // 收集紧邻的上方注释 298 | var currentLine = nodeLine - 1 299 | while (currentLine > 0) { 300 | val comments = commentsByLine[currentLine] 301 | if (comments != null) { 302 | result.addAll(0, comments) 303 | } else if (nodeLine - currentLine > 1) { 304 | // 如果出现空行,停止收集 305 | break 306 | } 307 | currentLine-- 308 | } 309 | 310 | return result 311 | } 312 | } 313 | 314 | // 文档注释相关的数据类 315 | data class DocComment( 316 | val description: String, 317 | val tags: List 318 | ) 319 | 320 | sealed class DocTag { 321 | abstract val name: String 322 | abstract var description: String 323 | } 324 | 325 | data class ParamTag( 326 | override val name: String, 327 | val type: Type, 328 | override var description: String 329 | ) : DocTag() 330 | 331 | data class ReturnTag( 332 | val type: Type, 333 | override val name: String = "return", 334 | override var description: String 335 | ) : DocTag() 336 | 337 | data class GenericTag( 338 | override val name: String, 339 | val constraint: Type?, 340 | override var description: String = "" 341 | ) : DocTag() 342 | 343 | data class TypeTag( 344 | val type: Type, 345 | override val name: String = "type", 346 | override var description: String 347 | ) : DocTag() 348 | 349 | data class ClassTag( 350 | override val name: String, 351 | override var description: String 352 | ) : DocTag() 353 | 354 | data class FieldTag( 355 | override val name: String, 356 | val type: Type, 357 | override var description: String 358 | ) : DocTag() 359 | 360 | // 添加新的文档标签类型 361 | data class MethodTag( 362 | val className: String, 363 | override val name: String, 364 | val type: FunctionType, 365 | override var description: String 366 | ) : DocTag() { 367 | val methodName = name 368 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/semantic/symbol/SymbolTable.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.semantic.symbol 2 | 3 | import io.github.dingyi222666.luaparser.parser.ast.node.Position 4 | import io.github.dingyi222666.luaparser.parser.ast.node.Range 5 | import io.github.dingyi222666.luaparser.semantic.types.Type 6 | 7 | class SymbolTable( 8 | private val parent: SymbolTable? = null, 9 | val range: Range? = null 10 | ) { 11 | private val symbols = mutableMapOf() 12 | private val children = mutableListOf() 13 | 14 | fun define( 15 | name: String, 16 | type: Type, 17 | kind: Symbol.Kind = Symbol.Kind.VARIABLE, 18 | range: Range? = null 19 | ): Symbol { 20 | val symbol = Symbol(name, type, kind, range) 21 | symbols[name] = symbol 22 | return symbol 23 | } 24 | 25 | // 基于位置查找最合适的符号表,优先匹配最近的作用域 26 | fun findTableAtPosition(position: Position): SymbolTable? { 27 | // 如果当前范围不包含该位置,直接返回null 28 | if (range != null && !range.contains(position)) { 29 | return null 30 | } 31 | 32 | // 查找所有匹配的子作用域 33 | val matchingChildren = children 34 | .mapNotNull { it.findTableAtPosition(position) } 35 | .sortedBy { it.range?.let { range -> 36 | // 计算范围大小,范围越小越精确 37 | (range.end.line - range.start.line) * 1000 + 38 | (range.end.column - range.start.column) 39 | } ?: Int.MAX_VALUE } 40 | 41 | // 返回范围最小的匹配作用域,如果没有则返回当前作用域 42 | return matchingChildren.firstOrNull() ?: this 43 | } 44 | 45 | // 在指定位置解析符号,优先从最近的作用域开始查找 46 | fun resolveAtPosition(name: String, position: Position): Symbol? { 47 | var currentTable = findTableAtPosition(position) 48 | 49 | // 如果找不到匹配的作用域,从父作用域查找 50 | if (currentTable == null) { 51 | return parent?.resolveAtPosition(name, position) 52 | } 53 | 54 | // 在当前作用域中查找 55 | var symbol = currentTable.symbols[name] 56 | 57 | // 如果当前作用域没找到,继续查找父作用域 58 | while (symbol == null && currentTable?.parent != null) { 59 | currentTable = currentTable.parent 60 | symbol = currentTable.symbols[name] 61 | } 62 | 63 | return symbol 64 | } 65 | 66 | // 从当前作用域解析符号 67 | fun resolve(name: String): Symbol? { 68 | return symbols[name] ?: parent?.resolve(name) 69 | } 70 | 71 | // 获取所有可见的符号,按作用域距离排序 72 | fun getAllVisibleSymbols(position: Position? = null): List { 73 | val result = mutableListOf() 74 | 75 | if (position != null) { 76 | // 找到最近的作用域 77 | var currentTable = findTableAtPosition(position) 78 | 79 | // 收集从最近作用域到根作用域的所有符号 80 | while (currentTable != null) { 81 | result.addAll(currentTable.symbols.values) 82 | currentTable = currentTable.parent 83 | } 84 | } else { 85 | // 如果没有指定位置,收集当前作用域及其父作用域的所有符号 86 | result.addAll(symbols.values) 87 | parent?.getAllVisibleSymbols()?.let { result.addAll(it) } 88 | } 89 | 90 | return result 91 | } 92 | 93 | fun createChild(range: Range? = null): SymbolTable { 94 | val child = SymbolTable(this, range) 95 | children.add(child) 96 | return child 97 | } 98 | 99 | fun getParent(): SymbolTable? = parent 100 | 101 | override fun toString(): String { 102 | return buildString { 103 | append("SymbolTable(") 104 | append("range=$range, ") 105 | append("symbols=${symbols.values.joinToString { "${it.name}: ${it.type.name}" }}, ") 106 | append("children=[") 107 | children.forEachIndexed { index, child -> 108 | if (index > 0) append(", ") 109 | append(child.toString()) 110 | } 111 | append("])") 112 | } 113 | } 114 | } 115 | 116 | data class Symbol( 117 | val name: String, 118 | val type: Type, 119 | val kind: Kind, 120 | val range: Range? = null 121 | ) { 122 | enum class Kind { 123 | VARIABLE, 124 | FUNCTION, 125 | PARAMETER, 126 | LOCAL, 127 | CLASS 128 | } 129 | 130 | override fun toString(): String { 131 | return "$name: ${type.name} (${kind.name})" 132 | } 133 | } 134 | 135 | // Range 扩展函数 136 | fun Range.contains(position: Position): Boolean { 137 | return when { 138 | position.line < start.line -> false 139 | position.line > end.line -> false 140 | position.line == start.line && position.column < start.column -> false 141 | position.line == end.line && position.column > end.column -> false 142 | else -> true 143 | } 144 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/semantic/types/Type.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.semantic.types 2 | 3 | sealed interface Type { 4 | val name: String 5 | 6 | fun isAssignableFrom(other: Type): Boolean 7 | fun union(other: Type): Type 8 | fun intersection(other: Type): Type 9 | } 10 | 11 | // 基础类型 12 | data class PrimitiveType( 13 | override val name: String, 14 | val kind: Kind 15 | ) : Type { 16 | enum class Kind { 17 | NIL, 18 | NUMBER, 19 | STRING, 20 | BOOLEAN, 21 | THREAD, 22 | USERDATA, 23 | ANY 24 | } 25 | 26 | override fun isAssignableFrom(other: Type): Boolean { 27 | if (this == ANY) return true 28 | if (other == NIL) return true 29 | return this == other 30 | } 31 | 32 | override fun union(other: Type): Type = UnionType(setOf(this, other)) 33 | override fun intersection(other: Type): Type = when { 34 | isAssignableFrom(other) -> other 35 | other.isAssignableFrom(this) -> this 36 | else -> NeverType 37 | } 38 | 39 | companion object { 40 | val NIL = PrimitiveType("nil", Kind.NIL) 41 | val NUMBER = PrimitiveType("number", Kind.NUMBER) 42 | val STRING = PrimitiveType("string", Kind.STRING) 43 | val BOOLEAN = PrimitiveType("boolean", Kind.BOOLEAN) 44 | val THREAD = PrimitiveType("thread", Kind.THREAD) 45 | val USERDATA = PrimitiveType("userdata", Kind.USERDATA) 46 | val ANY = PrimitiveType("any", Kind.ANY) 47 | } 48 | } 49 | 50 | // 函数类型 51 | data class FunctionType( 52 | val parameters: List, 53 | val returnType: Type, 54 | override val name: String = "function" 55 | ) : Type { 56 | override fun isAssignableFrom(other: Type): Boolean { 57 | if (other !is FunctionType) return false 58 | if (parameters.size != other.parameters.size) return false 59 | 60 | return parameters.zip(other.parameters).all { (a, b) -> 61 | a.type.isAssignableFrom(b.type) 62 | } && returnType.isAssignableFrom(other.returnType) 63 | } 64 | 65 | override fun union(other: Type): Type = UnionType(setOf(this, other)) 66 | override fun intersection(other: Type): Type = when { 67 | isAssignableFrom(other) -> other 68 | other.isAssignableFrom(this) -> this 69 | else -> NeverType 70 | } 71 | } 72 | 73 | // 表类型 74 | data class TableType( 75 | val fields: Map, 76 | val indexSignature: IndexSignature? = null, 77 | override val name: String = "table" 78 | ) : Type { 79 | data class IndexSignature( 80 | val keyType: Type, 81 | val valueType: Type 82 | ) 83 | 84 | override fun isAssignableFrom(other: Type): Boolean { 85 | if (other !is TableType) return false 86 | 87 | // 检查所有字段 88 | if (!fields.all { (key, type) -> 89 | other.fields[key]?.let { type.isAssignableFrom(it) } ?: false 90 | }) return false 91 | 92 | // 检查索引签名 93 | if (indexSignature != null) { 94 | if (other.indexSignature == null) return false 95 | if (!indexSignature.keyType.isAssignableFrom(other.indexSignature.keyType)) return false 96 | if (!indexSignature.valueType.isAssignableFrom(other.indexSignature.valueType)) return false 97 | } 98 | 99 | return true 100 | } 101 | 102 | override fun union(other: Type): Type = UnionType(setOf(this, other)) 103 | override fun intersection(other: Type): Type = when { 104 | isAssignableFrom(other) -> other 105 | other.isAssignableFrom(this) -> this 106 | else -> NeverType 107 | } 108 | } 109 | 110 | // 联合类型 111 | data class UnionType( 112 | val types: Set, 113 | override val name: String = types.joinToString("|") { it.name } 114 | ) : Type { 115 | override fun isAssignableFrom(other: Type): Boolean { 116 | return types.any { it.isAssignableFrom(other) } 117 | } 118 | 119 | override fun union(other: Type): Type = UnionType(types + other) 120 | override fun intersection(other: Type): Type = when(other) { 121 | is UnionType -> UnionType(types.intersect(other.types)) 122 | else -> types.firstOrNull { it.isAssignableFrom(other) } ?: NeverType 123 | } 124 | } 125 | 126 | // Never类型 127 | object NeverType : Type { 128 | override val name: String = "never" 129 | override fun isAssignableFrom(other: Type): Boolean = false 130 | override fun union(other: Type): Type = other 131 | override fun intersection(other: Type): Type = this 132 | } 133 | 134 | data class ParameterType( 135 | val name: String, 136 | val type: Type, 137 | val optional: Boolean = false, 138 | val vararg: Boolean = false 139 | ) 140 | 141 | // 添加 VarArgType 类型 142 | data class VarArgType( 143 | val types: List, 144 | override val name: String = "vararg<${types.joinToString(", ") { it.name }}>" 145 | ) : Type { 146 | override fun isAssignableFrom(other: Type): Boolean { 147 | return when (other) { 148 | is VarArgType -> { 149 | if (types.size != other.types.size) return false 150 | types.zip(other.types).all { (a, b) -> a.isAssignableFrom(b) } 151 | } 152 | 153 | else -> false 154 | } 155 | } 156 | 157 | override fun union(other: Type): Type = UnionType(setOf(this, other)) 158 | 159 | override fun intersection(other: Type): Type = when { 160 | isAssignableFrom(other) -> other 161 | other.isAssignableFrom(this) -> this 162 | else -> NeverType 163 | } 164 | } 165 | 166 | // 泛型类型 167 | data class GenericType( 168 | val baseName: String, 169 | val typeParameters: List, 170 | override val name: String = "$baseName<${typeParameters.joinToString(", ") { it.name }}>" 171 | ) : Type { 172 | override fun isAssignableFrom(other: Type): Boolean { 173 | if (other !is GenericType) return false 174 | if (baseName != other.baseName) return false 175 | if (typeParameters.size != other.typeParameters.size) return false 176 | 177 | return typeParameters.zip(other.typeParameters).all { (a, b) -> 178 | a.isAssignableFrom(b) 179 | } 180 | } 181 | 182 | override fun union(other: Type): Type = UnionType(setOf(this, other)) 183 | override fun intersection(other: Type): Type = when { 184 | isAssignableFrom(other) -> other 185 | other.isAssignableFrom(this) -> this 186 | else -> NeverType 187 | } 188 | } 189 | 190 | // 数组类型 191 | data class ArrayType( 192 | val elementType: Type, 193 | override val name: String = "${elementType.name}[]" 194 | ) : Type { 195 | override fun isAssignableFrom(other: Type): Boolean { 196 | return when (other) { 197 | is ArrayType -> elementType.isAssignableFrom(other.elementType) 198 | // 特殊处理:允许将表类型赋值给数组类型,如果表的索引签名匹配 199 | is TableType -> { 200 | other.indexSignature?.let { signature -> 201 | signature.keyType == PrimitiveType.NUMBER && 202 | elementType.isAssignableFrom(signature.valueType) 203 | } ?: false 204 | } 205 | else -> false 206 | } 207 | } 208 | 209 | override fun union(other: Type): Type = when (other) { 210 | is ArrayType -> ArrayType(elementType.union(other.elementType)) 211 | else -> UnionType(setOf(this, other)) 212 | } 213 | 214 | override fun intersection(other: Type): Type = when { 215 | isAssignableFrom(other) -> other 216 | other.isAssignableFrom(this) -> this 217 | other is ArrayType -> ArrayType(elementType.intersection(other.elementType)) 218 | else -> NeverType 219 | } 220 | } 221 | 222 | // 自定义类型(用于处理未知的类型名称) 223 | data class CustomType( 224 | override val name: String 225 | ) : Type { 226 | override fun isAssignableFrom(other: Type): Boolean { 227 | return other is CustomType && name == other.name 228 | } 229 | 230 | override fun union(other: Type): Type = UnionType(setOf(this, other)) 231 | override fun intersection(other: Type): Type = when { 232 | isAssignableFrom(other) -> other 233 | other.isAssignableFrom(this) -> this 234 | else -> NeverType 235 | } 236 | } 237 | 238 | // 类类型 239 | data class ClassType( 240 | override val name: String, 241 | val fields: Map = mapOf(), 242 | val methods: Map = mapOf(), 243 | val parent: ClassType? = null, 244 | val typeParameters: List = emptyList() 245 | ) : Type { 246 | override fun isAssignableFrom(other: Type): Boolean { 247 | if (other is ClassType) { 248 | // 检查是否是同一个类或其子类 249 | var current: ClassType? = other 250 | while (current != null) { 251 | if (current.name == name) { 252 | // 检查泛型参数 253 | if (typeParameters.size != current.typeParameters.size) return false 254 | if (!typeParameters.zip(current.typeParameters).all { (a, b) -> 255 | a.isAssignableFrom(b) 256 | }) return false 257 | return true 258 | } 259 | current = current.parent 260 | } 261 | } 262 | return false 263 | } 264 | 265 | override fun union(other: Type): Type = UnionType(setOf(this, other)) 266 | 267 | override fun intersection(other: Type): Type = when { 268 | isAssignableFrom(other) -> other 269 | other.isAssignableFrom(this) -> this 270 | else -> NeverType 271 | } 272 | 273 | // 获取类的所有字段(包括继承的) 274 | fun getAllFields(): Map { 275 | val allFields = mutableMapOf() 276 | var current: ClassType? = this 277 | while (current != null) { 278 | allFields.putAll(current.fields) 279 | current = current.parent 280 | } 281 | return allFields 282 | } 283 | 284 | // 获取类的所有方法(包括继承的) 285 | fun getAllMethods(): Map { 286 | val allMethods = mutableMapOf() 287 | var current: ClassType? = this 288 | while (current != null) { 289 | allMethods.putAll(current.methods) 290 | current = current.parent 291 | } 292 | return allMethods 293 | } 294 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/semantic/types/TypeAnnotationParser.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.semantic.types 2 | 3 | class TypeAnnotationParser { 4 | // 存储已定义的类 5 | private val classes = mutableMapOf() 6 | 7 | fun parse(input: String): Type { 8 | val trimmed = input.trim() 9 | 10 | // 基础类型处理 11 | when { 12 | trimmed.startsWith("overload") -> { 13 | // 移除 overload 前缀并解析剩余部分 14 | return parse(trimmed.substring("overload".length).trim()) 15 | } 16 | trimmed == "string" -> return PrimitiveType.STRING 17 | trimmed == "number" -> return PrimitiveType.NUMBER 18 | trimmed == "boolean" -> return PrimitiveType.BOOLEAN 19 | trimmed == "nil" -> return PrimitiveType.NIL 20 | trimmed == "any" -> return PrimitiveType.ANY 21 | trimmed == "void" -> return PrimitiveType.NIL 22 | } 23 | 24 | // 检查是否是已定义的类 25 | classes[trimmed]?.let { return it } 26 | 27 | // 函数类型处理 - 要在泛型处理之前 28 | if (trimmed.startsWith("fun")) { 29 | return parseFunctionType(trimmed) 30 | } 31 | 32 | // 联合类型处理 - 要在泛型处理之前 33 | if (trimmed.contains("|")) { 34 | return parseUnionType(trimmed) 35 | } 36 | 37 | // 泛型处理 38 | if (trimmed.contains("<")) { 39 | return parseGenericType(trimmed) 40 | } 41 | 42 | // 数组类型 43 | if (trimmed.endsWith("[]")) { 44 | val elementType = parse(trimmed.substring(0, trimmed.length - 2)) 45 | return ArrayType(elementType) 46 | } 47 | 48 | return CustomType(trimmed) 49 | } 50 | 51 | private fun parseGenericType(input: String): Type { 52 | var depth = 0 53 | var start = input.indexOf("<") + 1 54 | val name = input.substring(0, start - 1).trim() 55 | val params = mutableListOf() 56 | var current = start 57 | 58 | while (current < input.length) { 59 | when (input[current]) { 60 | '<' -> depth++ 61 | '>' -> { 62 | depth-- 63 | if (depth < 0) { 64 | if (current > start) { 65 | params.add(parse(input.substring(start, current).trim())) 66 | } 67 | break 68 | } 69 | } 70 | ',' -> { 71 | if (depth == 0) { 72 | params.add(parse(input.substring(start, current).trim())) 73 | start = current + 1 74 | } 75 | } 76 | } 77 | current++ 78 | } 79 | 80 | // 检查是否是类的泛型实例 81 | classes[name]?.let { classType -> 82 | return ClassType( 83 | name = classType.name, 84 | fields = classType.fields, 85 | methods = classType.methods, 86 | parent = classType.parent, 87 | typeParameters = params 88 | ) 89 | } 90 | 91 | return GenericType(name, params) 92 | } 93 | 94 | private fun parseFunctionType(input: String): FunctionType { 95 | try { 96 | // 移除 fun 前缀和周围的空格 97 | var remaining = input.substring(3).trim() 98 | 99 | // 解析参数列表 100 | val params = mutableListOf() 101 | if (remaining.startsWith("(")) { 102 | val paramEnd = findMatchingParenthesis(remaining) 103 | if (paramEnd == -1) { 104 | throw IllegalArgumentException("Unmatched parentheses in function type") 105 | } 106 | 107 | val paramStr = remaining.substring(1, paramEnd).trim() 108 | if (paramStr.isNotEmpty()) { 109 | params.addAll(parseParameters(paramStr)) 110 | } 111 | 112 | remaining = remaining.substring(paramEnd + 1).trim() 113 | } 114 | 115 | // 解析返回类型 116 | var returnType: Type = PrimitiveType.NIL 117 | if (remaining.startsWith(":")) { 118 | returnType = parse(remaining.substring(1).trim()) 119 | } 120 | 121 | return FunctionType(params, returnType) 122 | } catch (e: Exception) { 123 | throw IllegalArgumentException("Invalid function type: $input", e) 124 | } 125 | } 126 | 127 | private fun parseParameters(paramStr: String): List { 128 | if (paramStr.isEmpty()) return emptyList() 129 | 130 | val params = mutableListOf() 131 | var depth = 0 132 | var start = 0 133 | var current = 0 134 | 135 | while (current < paramStr.length) { 136 | when (paramStr[current]) { 137 | '<' -> depth++ 138 | '>' -> depth-- 139 | '|' -> if (depth == 0) depth = depth // 忽略联合类型中的 | 140 | ',' -> { 141 | if (depth == 0) { 142 | parseParameter(paramStr.substring(start, current))?.let { params.add(it) } 143 | start = current + 1 144 | } 145 | } 146 | } 147 | current++ 148 | } 149 | 150 | // 处理最后一个参数 151 | if (start < paramStr.length) { 152 | parseParameter(paramStr.substring(start))?.let { params.add(it) } 153 | } 154 | 155 | return params 156 | } 157 | 158 | private fun parseParameter(paramStr: String): ParameterType? { 159 | val trimmed = paramStr.trim() 160 | if (trimmed.isEmpty()) return null 161 | 162 | val parts = trimmed.split(":") 163 | if (parts.size != 2) { 164 | throw IllegalArgumentException("Invalid parameter format: $paramStr") 165 | } 166 | 167 | val name = parts[0].trim() 168 | val type = parse(parts[1].trim()) 169 | return ParameterType(name, type) 170 | } 171 | 172 | private fun findMatchingParenthesis(input: String): Int { 173 | var depth = 0 174 | input.forEachIndexed { index, char -> 175 | when (char) { 176 | '(' -> depth++ 177 | ')' -> { 178 | depth-- 179 | if (depth == 0) return index 180 | } 181 | } 182 | } 183 | return -1 184 | } 185 | 186 | fun defineClass( 187 | name: String, 188 | fields: Map = mapOf(), 189 | methods: Map = mapOf(), 190 | parent: String? = null, 191 | typeParameters: List = emptyList() 192 | ): ClassType { 193 | val parentClass = parent?.let { classes[it] } 194 | val classType = ClassType( 195 | name = name, 196 | fields = fields, 197 | methods = methods, 198 | parent = parentClass, 199 | typeParameters = typeParameters 200 | ) 201 | classes[name] = classType 202 | return classType 203 | } 204 | 205 | private fun parseUnionType(input: String): Type { 206 | var depth = 0 207 | var start = 0 208 | val types = mutableSetOf() 209 | 210 | for (i in input.indices) { 211 | when (input[i]) { 212 | '<' -> depth++ 213 | '>' -> depth-- 214 | '|' -> { 215 | if (depth == 0) { 216 | val typeStr = input.substring(start, i).trim() 217 | if (typeStr.isNotEmpty()) { 218 | types.add(parse(typeStr)) 219 | } 220 | start = i + 1 221 | } 222 | } 223 | } 224 | } 225 | 226 | // 处理最后一个类型 227 | val lastType = input.substring(start).trim() 228 | if (lastType.isNotEmpty()) { 229 | types.add(parse(lastType)) 230 | } 231 | 232 | return UnionType(types) 233 | } 234 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/semantic/types/TypeContext.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.semantic.types 2 | 3 | class TypeContext( 4 | private val parent: TypeContext? = null 5 | ) { 6 | private val types = mutableMapOf() 7 | 8 | fun defineType(name: String, type: Type) { 9 | types[name] = type 10 | } 11 | 12 | fun getType(name: String): Type { 13 | return types[name] ?: parent?.getType(name) ?: PrimitiveType.ANY 14 | } 15 | 16 | fun hasType(name: String): Boolean { 17 | return types.containsKey(name) || (parent?.hasType(name) ?: false) 18 | } 19 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/semantic/types/TypeInferer.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.semantic.types 2 | 3 | import io.github.dingyi222666.luaparser.parser.ast.node.* 4 | import io.github.dingyi222666.luaparser.semantic.Diagnostic 5 | 6 | class TypeInferer { 7 | private val diagnostics = mutableListOf() 8 | 9 | fun inferType(node: ExpressionNode, context: TypeContext): Type { 10 | diagnostics.clear() 11 | return doInferType(node, context) 12 | } 13 | 14 | fun getDiagnostics(): List = diagnostics.toList() 15 | 16 | private fun doInferType(node: ExpressionNode, context: TypeContext): Type { 17 | return when (node) { 18 | is ConstantNode -> inferConstantType(node) 19 | is BinaryExpression -> inferBinaryExpression(node, context) 20 | is UnaryExpression -> inferUnaryExpression(node, context) 21 | is CallExpression -> inferCallExpression(node, context) 22 | is TableConstructorExpression -> inferTableConstructor(node, context) 23 | is ArrayConstructorExpression -> inferArrayConstructor(node, context) 24 | is FunctionDeclaration -> inferFunctionDeclaration(node, context) 25 | is LambdaDeclaration -> inferLambdaDeclaration(node, context) 26 | is MemberExpression -> inferMemberExpression(node, context) 27 | is IndexExpression -> inferIndexExpression(node, context) 28 | is Identifier -> context.getType(node.name) 29 | else -> PrimitiveType.ANY 30 | } 31 | } 32 | 33 | private fun inferConstantType(node: ConstantNode): Type { 34 | return when (node.constantType) { 35 | ConstantNode.TYPE.NIL -> PrimitiveType.NIL 36 | ConstantNode.TYPE.BOOLEAN -> PrimitiveType.BOOLEAN 37 | ConstantNode.TYPE.STRING -> PrimitiveType.STRING 38 | ConstantNode.TYPE.INTERGER, ConstantNode.TYPE.FLOAT -> PrimitiveType.NUMBER 39 | ConstantNode.TYPE.UNKNOWN -> PrimitiveType.ANY 40 | } 41 | } 42 | 43 | private fun inferArrayConstructor( 44 | node: ArrayConstructorExpression, 45 | context: TypeContext 46 | ): TableType { 47 | val elementTypes = node.values.map { inferType(it, context) }.toSet() 48 | val elementType = when { 49 | elementTypes.isEmpty() -> PrimitiveType.ANY 50 | elementTypes.size == 1 -> elementTypes.first() 51 | else -> UnionType(elementTypes) 52 | } 53 | 54 | return TableType( 55 | fields = emptyMap(), 56 | indexSignature = TableType.IndexSignature( 57 | PrimitiveType.NUMBER, 58 | elementType 59 | ) 60 | ) 61 | } 62 | 63 | private fun inferMemberExpression( 64 | node: MemberExpression, 65 | context: TypeContext 66 | ): Type { 67 | val baseType = inferType(node.base, context) 68 | return when (baseType) { 69 | is TableType -> { 70 | baseType.fields[node.identifier.name] ?: run { 71 | val fullPath = buildMemberPath(node) 72 | context.getType(fullPath).takeIf { it != PrimitiveType.ANY } 73 | ?: baseType.indexSignature?.valueType 74 | ?: PrimitiveType.ANY 75 | } 76 | } 77 | 78 | else -> PrimitiveType.ANY 79 | } 80 | } 81 | 82 | private fun buildMemberPath(node: MemberExpression): String { 83 | return when (val base = node.base) { 84 | is MemberExpression -> "${buildMemberPath(base)}.${node.identifier.name}" 85 | else -> "${base}.${node.identifier.name}" 86 | } 87 | } 88 | 89 | private fun inferIndexExpression( 90 | node: IndexExpression, 91 | context: TypeContext 92 | ): Type { 93 | val baseType = inferType(node.base, context) 94 | val indexType = inferType(node.index, context) 95 | 96 | return when (baseType) { 97 | is TableType -> { 98 | baseType.indexSignature?.let { signature -> 99 | if (signature.keyType.isAssignableFrom(indexType)) { 100 | signature.valueType 101 | } else { 102 | PrimitiveType.ANY 103 | } 104 | } ?: PrimitiveType.ANY 105 | } 106 | 107 | else -> PrimitiveType.ANY 108 | } 109 | } 110 | 111 | private fun inferLambdaDeclaration( 112 | node: LambdaDeclaration, 113 | context: TypeContext 114 | ): FunctionType { 115 | val paramTypes = node.params.map { param -> 116 | ParameterType(param.name, context.getType(param.name)) 117 | } 118 | 119 | val functionContext = TypeContext(parent = context) 120 | node.params.forEach { param -> 121 | functionContext.defineType(param.name, PrimitiveType.ANY) 122 | } 123 | 124 | val returnType = inferType(node.expression, functionContext) 125 | 126 | return FunctionType(paramTypes, returnType) 127 | } 128 | 129 | private fun inferBinaryExpression(node: BinaryExpression, context: TypeContext): Type { 130 | val leftType = inferType(node.left!!, context) 131 | val rightType = inferType(node.right!!, context) 132 | 133 | return when (node.operator) { 134 | ExpressionOperator.ADD, 135 | ExpressionOperator.MINUS, 136 | ExpressionOperator.MULT, 137 | ExpressionOperator.DIV, 138 | ExpressionOperator.MOD -> { 139 | if (leftType != PrimitiveType.NUMBER || rightType != PrimitiveType.NUMBER) { 140 | diagnostics.add( 141 | Diagnostic( 142 | range = node.range, 143 | message = "Operator '${node.operator}' cannot be applied to types '${leftType.name}' and '${rightType.name}'", 144 | severity = Diagnostic.Severity.ERROR 145 | ) 146 | ) 147 | } 148 | PrimitiveType.NUMBER 149 | } 150 | 151 | ExpressionOperator.CONCAT -> PrimitiveType.STRING 152 | 153 | ExpressionOperator.LT, 154 | ExpressionOperator.GT, 155 | ExpressionOperator.LE, 156 | ExpressionOperator.GE, 157 | ExpressionOperator.EQ, 158 | ExpressionOperator.NE -> PrimitiveType.BOOLEAN 159 | 160 | else -> PrimitiveType.ANY 161 | } 162 | } 163 | 164 | private fun inferUnaryExpression(node: UnaryExpression, context: TypeContext): Type { 165 | return when (node.operator) { 166 | ExpressionOperator.MINUS, 167 | ExpressionOperator.BIT_TILDE -> PrimitiveType.NUMBER 168 | 169 | ExpressionOperator.NOT -> PrimitiveType.BOOLEAN 170 | ExpressionOperator.GETLEN -> PrimitiveType.NUMBER 171 | else -> PrimitiveType.ANY 172 | } 173 | } 174 | 175 | private fun inferCallExpression(node: CallExpression, context: TypeContext): Type { 176 | val funcType = when (val base = node.base) { 177 | is MemberExpression -> { 178 | val baseType = inferType(base.base, context) 179 | when (baseType) { 180 | is TableType -> { 181 | val methodType = baseType.fields[base.identifier.name] 182 | if (methodType is FunctionType) { 183 | methodType 184 | } else { 185 | diagnostics.add( 186 | Diagnostic( 187 | range = base.range, 188 | message = "Member '${base.identifier.name}' is not a function", 189 | severity = Diagnostic.Severity.ERROR 190 | ) 191 | ) 192 | return PrimitiveType.ANY 193 | } 194 | } 195 | 196 | else -> { 197 | diagnostics.add( 198 | Diagnostic( 199 | range = base.base.range, 200 | message = "Cannot read property '${base.identifier.name}' of type '${baseType.name}'", 201 | severity = Diagnostic.Severity.ERROR 202 | ) 203 | ) 204 | return PrimitiveType.ANY 205 | } 206 | } 207 | } 208 | 209 | else -> { 210 | val type = inferType(base, context) 211 | if (type !is FunctionType) { 212 | diagnostics.add( 213 | Diagnostic( 214 | range = base.range, 215 | message = "Value of type '${type.name}' is not callable", 216 | severity = Diagnostic.Severity.ERROR 217 | ) 218 | ) 219 | return PrimitiveType.ANY 220 | } 221 | type 222 | } 223 | } 224 | 225 | val argumentTypes = node.arguments.map { inferType(it, context) } 226 | if (argumentTypes.size != funcType.parameters.size) { 227 | diagnostics.add( 228 | Diagnostic( 229 | range = node.range, 230 | message = "Expected ${funcType.parameters.size} arguments, but got ${argumentTypes.size}", 231 | severity = Diagnostic.Severity.ERROR 232 | ) 233 | ) 234 | return PrimitiveType.ANY 235 | } 236 | 237 | argumentTypes.zip(funcType.parameters).forEachIndexed { index, (argType, param) -> 238 | if (!param.type.isAssignableFrom(argType)) { 239 | diagnostics.add( 240 | Diagnostic( 241 | range = node.arguments[index].range, 242 | message = "Argument of type '${argType.name}' is not assignable to parameter of type '${param.type.name}'", 243 | severity = Diagnostic.Severity.ERROR 244 | ) 245 | ) 246 | } 247 | } 248 | 249 | return funcType.returnType 250 | } 251 | 252 | private fun inferTableConstructor( 253 | node: TableConstructorExpression, 254 | context: TypeContext 255 | ): TableType { 256 | val fields = mutableMapOf() 257 | 258 | node.fields.forEach { field -> 259 | when (field) { 260 | is TableKeyString -> { 261 | val key = field.key 262 | val valueExpr = field.value 263 | 264 | when (key) { 265 | is Identifier -> { 266 | val valueType = when (valueExpr) { 267 | is Identifier -> { 268 | context.getType(valueExpr.name) 269 | } 270 | 271 | is MemberExpression -> { 272 | inferMemberExpression(valueExpr, context) 273 | } 274 | 275 | else -> inferType(valueExpr, context) 276 | } 277 | 278 | fields[key.name] = valueType 279 | } 280 | 281 | is ConstantNode -> { 282 | fields[key.stringOf()] = inferType(valueExpr, context) 283 | } 284 | } 285 | } 286 | 287 | is TableKey -> { 288 | when (val key = field.key) { 289 | is ConstantNode -> { 290 | when (key.constantType) { 291 | ConstantNode.TYPE.INTERGER -> { 292 | fields[key.intOf().toString()] = inferType(field.value, context) 293 | } 294 | 295 | ConstantNode.TYPE.STRING -> { 296 | fields[key.stringOf()] = inferType(field.value, context) 297 | } 298 | 299 | else -> {} 300 | } 301 | } 302 | } 303 | } 304 | } 305 | } 306 | 307 | return TableType(fields) 308 | } 309 | 310 | private fun inferFunctionDeclaration( 311 | node: FunctionDeclaration, 312 | context: TypeContext 313 | ): FunctionType { 314 | val functionContext = TypeContext(parent = context) 315 | 316 | // 处理参数 317 | val paramTypes = node.params.map { param -> 318 | val paramType = context.getType(param.name).takeIf { it != PrimitiveType.ANY } 319 | ?: PrimitiveType.ANY 320 | functionContext.defineType(param.name, paramType) 321 | ParameterType(param.name, paramType) 322 | } 323 | 324 | // 推导返回类型 325 | val returnType = node.body?.let { body -> 326 | inferFunctionReturnType(body, functionContext) 327 | } ?: PrimitiveType.NIL 328 | 329 | return FunctionType(paramTypes, returnType) 330 | } 331 | 332 | private fun inferFunctionReturnType(body: BlockNode, context: TypeContext): Type { 333 | val returnStatement = body.returnStatement 334 | 335 | return when { 336 | returnStatement == null -> PrimitiveType.NIL 337 | returnStatement.arguments.isEmpty() -> PrimitiveType.NIL 338 | returnStatement.arguments.size == 1 -> inferType(returnStatement.arguments[0], context) 339 | else -> { 340 | // 多个返回值,创建 VarArgType 341 | val returnTypes = returnStatement.arguments.map { inferType(it, context) } 342 | VarArgType(returnTypes) 343 | } 344 | } 345 | } 346 | } 347 | 348 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/source/AST2Lua.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.source 2 | 3 | import io.github.dingyi222666.luaparser.parser.ast.node.* 4 | import io.github.dingyi222666.luaparser.parser.ast.visitor.ASTVisitor 5 | import kotlin.math.max 6 | 7 | /** 8 | * @author: dingyi 9 | * @date: 2023/2/10 10 | * @description: 11 | **/ 12 | class AST2Lua : ASTVisitor { 13 | 14 | private var currentDepth = -1 15 | 16 | var indentSize = 4 17 | 18 | private fun indent(): String { 19 | return " ".repeat(indentSize).repeat(max(currentDepth, 0)) 20 | } 21 | 22 | private fun indent(sb: StringBuilder) { 23 | sb.append(indent()) 24 | } 25 | 26 | private fun appendLineAndIndent(value: StringBuilder) { 27 | value.appendLine() 28 | indent(value) 29 | } 30 | 31 | override fun visitBlockNode(node: BlockNode, value: StringBuilder) { 32 | currentDepth++ 33 | super.visitBlockNode(node, value) 34 | currentDepth-- 35 | appendLineAndIndent(value) 36 | } 37 | 38 | override fun visitStatementNode(node: StatementNode, value: StringBuilder) { 39 | appendLineAndIndent(value) 40 | super.visitStatementNode(node, value) 41 | } 42 | 43 | fun asCode(node: ChunkNode): String { 44 | val builder = StringBuilder() 45 | visitChunkNode(node, builder) 46 | return builder.toString() 47 | } 48 | 49 | 50 | override fun visitAssignmentStatement(node: AssignmentStatement, value: StringBuilder) { 51 | node.init.forEachIndexed { index, baseASTNode -> 52 | if (index != 0) { 53 | value.append(", ") 54 | } 55 | visitExpressionNode(baseASTNode, value) 56 | } 57 | value.append(" = ") 58 | node.variables.forEachIndexed { index, baseASTNode -> 59 | if (index != 0) { 60 | value.append(", ") 61 | } 62 | visitExpressionNode(baseASTNode, value) 63 | } 64 | } 65 | 66 | override fun visitConstantNode(node: ConstantNode, value: StringBuilder) { 67 | value.append(node.rawValue) 68 | } 69 | 70 | override fun visitBinaryExpression(node: BinaryExpression, value: StringBuilder) { 71 | node.left?.let { visitExpressionNode(it, value) } 72 | value.append(" ${operatorToLua(node.operator)} ") 73 | node.right?.let { visitExpressionNode(it, value) } 74 | } 75 | 76 | override fun visitUnaryExpression(node: UnaryExpression, value: StringBuilder) { 77 | value.append(operatorToLua(node.operator)) 78 | visitExpressionNode(node.arg, value) 79 | } 80 | 81 | override fun visitCallExpression(node: CallExpression, value: StringBuilder) { 82 | visitExpressionNode(node.base, value) 83 | value.append("(") 84 | node.arguments.forEachIndexed { index, baseASTNode -> 85 | if (index != 0) { 86 | value.append(", ") 87 | } 88 | visitExpressionNode(baseASTNode, value) 89 | } 90 | value.append(")") 91 | } 92 | 93 | 94 | override fun visitFunctionDeclaration(node: FunctionDeclaration, value: StringBuilder) { 95 | value.append("function ") 96 | node.identifier?.let { visitExpressionNode(it, value) } 97 | value.append("(") 98 | node.params.forEachIndexed { index, baseASTNode -> 99 | if (index != 0) { 100 | value.append(", ") 101 | } 102 | visitExpressionNode(baseASTNode, value) 103 | } 104 | value.append(")") 105 | 106 | node.body?.let { visitBlockNode(it, value) } 107 | 108 | value.append("end") 109 | } 110 | 111 | override fun visitArrayConstructorExpression(node: ArrayConstructorExpression, value: StringBuilder) { 112 | value.append("[") 113 | node.values.forEachIndexed { index, baseASTNode -> 114 | if (index != 0) { 115 | value.append(", ") 116 | } 117 | visitExpressionNode(baseASTNode, value) 118 | } 119 | value.append("]") 120 | } 121 | 122 | override fun visitBreakStatement(node: BreakStatement, value: StringBuilder) { 123 | indent(value) 124 | value.appendLine("break") 125 | } 126 | 127 | override fun visitCaseCause(node: CaseCause, value: StringBuilder) { 128 | appendLineAndIndent(value) 129 | value.append("case ") 130 | node.conditions.forEachIndexed { index, baseASTNode -> 131 | if (index != 0) { 132 | value.append(", ") 133 | } 134 | visitExpressionNode(baseASTNode, value) 135 | } 136 | value.append(" then") 137 | 138 | visitBlockNode(node.body, value) 139 | 140 | } 141 | 142 | 143 | override fun visitCommentStatement(commentStatement: CommentStatement, value: StringBuilder) { 144 | value.append(commentStatement.comment) 145 | } 146 | 147 | override fun visitCallStatement(node: CallStatement, value: StringBuilder) { 148 | visitExpressionNode(node.expression, value) 149 | } 150 | 151 | override fun visitContinueStatement(node: ContinueStatement, value: StringBuilder) { 152 | value.appendLine("continue") 153 | } 154 | 155 | override fun visitDefaultCause(node: DefaultCause, value: StringBuilder) { 156 | appendLineAndIndent(value) 157 | value.append("default") 158 | 159 | visitBlockNode(node.body, value) 160 | 161 | 162 | } 163 | 164 | override fun visitDoStatement(node: DoStatement, value: StringBuilder) { 165 | value.append("do") 166 | 167 | visitBlockNode(node.body, value) 168 | 169 | value.append("end") 170 | } 171 | 172 | override fun visitElseClause(node: ElseClause, value: StringBuilder) { 173 | appendLineAndIndent(value) 174 | value.append("else") 175 | 176 | visitBlockNode(node.body, value) 177 | 178 | } 179 | 180 | override fun visitElseIfClause(node: ElseIfClause, value: StringBuilder) { 181 | appendLineAndIndent(value) 182 | value.append("elseif ") 183 | visitExpressionNode(node.condition, value) 184 | value.append(" then") 185 | 186 | appendLineAndIndent(value) 187 | visitBlockNode(node.body, value) 188 | } 189 | 190 | override fun visitForGenericStatement(node: ForGenericStatement, value: StringBuilder) { 191 | value.append("for ") 192 | node.variables.forEachIndexed { index, baseASTNode -> 193 | if (index != 0) { 194 | value.append(", ") 195 | } 196 | visitExpressionNode(baseASTNode, value) 197 | } 198 | value.append(" in ") 199 | node.iterators.forEachIndexed { index, baseASTNode -> 200 | if (index != 0) { 201 | value.append(", ") 202 | } 203 | visitExpressionNode(baseASTNode, value) 204 | } 205 | value.append(" do") 206 | 207 | visitBlockNode(node.body, value) 208 | 209 | value.append("end") 210 | } 211 | 212 | override fun visitIdentifier(node: Identifier, value: StringBuilder) { 213 | value.append(node.name) 214 | } 215 | 216 | override fun visitGotoStatement(node: GotoStatement, value: StringBuilder) { 217 | value.append("goto ") 218 | visitExpressionNode(node.identifier, value) 219 | value.appendLine() 220 | } 221 | 222 | override fun visitIfClause(node: IfClause, value: StringBuilder) { 223 | appendLineAndIndent(value) 224 | value.append("if ") 225 | visitExpressionNode(node.condition, value) 226 | value.append(" then") 227 | indent(value) 228 | 229 | visitBlockNode(node.body, value) 230 | 231 | } 232 | 233 | override fun visitMemberExpression(node: MemberExpression, value: StringBuilder) { 234 | visitExpressionNode(node.base, value) 235 | value.append(node.indexer) 236 | visitExpressionNode(node.identifier, value) 237 | } 238 | 239 | override fun visitReturnStatement(node: ReturnStatement, value: StringBuilder) { 240 | value.append("return ") 241 | node.arguments.forEachIndexed { index, baseASTNode -> 242 | if (index != 0) { 243 | value.append(", ") 244 | } 245 | visitExpressionNode(baseASTNode, value) 246 | } 247 | } 248 | 249 | override fun visitIndexExpression(node: IndexExpression, value: StringBuilder) { 250 | visitExpressionNode(node.base, value) 251 | value.append("[") 252 | visitExpressionNode(node.index, value) 253 | value.append("]") 254 | } 255 | 256 | override fun visitForNumericStatement(node: ForNumericStatement, value: StringBuilder) { 257 | value.append("for ") 258 | visitExpressionNode(node.variable, value) 259 | value.append(" = ") 260 | visitExpressionNode(node.start, value) 261 | value.append(", ") 262 | visitExpressionNode(node.end, value) 263 | node.step?.let { 264 | value.append(", ") 265 | visitExpressionNode(it, value) 266 | } 267 | value.append(" do ") 268 | 269 | 270 | visitBlockNode(node.body, value) 271 | 272 | 273 | indent(value) 274 | value.appendLine("end") 275 | } 276 | 277 | 278 | override fun visitLabelStatement(node: LabelStatement, value: StringBuilder) { 279 | value.append("::") 280 | visitExpressionNode(node.identifier, value) 281 | value.appendLine("::") 282 | } 283 | 284 | override fun visitLambdaDeclaration(node: LambdaDeclaration, value: StringBuilder) { 285 | value.append("lambda ") 286 | if (node.params.isNotEmpty()) { 287 | value.append("(") 288 | node.params.forEachIndexed { index, baseASTNode -> 289 | if (index != 0) { 290 | value.append(", ") 291 | } 292 | visitExpressionNode(baseASTNode, value) 293 | } 294 | value.append(")") 295 | } 296 | value.append(" : ") 297 | visitExpressionNode(node.expression, value) 298 | } 299 | 300 | override fun visitRepeatStatement(node: RepeatStatement, value: StringBuilder) { 301 | value.append("repeat") 302 | 303 | visitBlockNode(node.body, value) 304 | 305 | indent(value) 306 | value.append("until ") 307 | visitExpressionNode(node.condition, value) 308 | } 309 | 310 | override fun visitStringCallExpression(node: StringCallExpression, value: StringBuilder) { 311 | visitExpressionNode(node.base, value) 312 | value.append("(") 313 | node.arguments.forEachIndexed { index, baseASTNode -> 314 | if (index != 0) { 315 | value.append(", ") 316 | } 317 | visitExpressionNode(baseASTNode, value) 318 | } 319 | value.append(")") 320 | } 321 | 322 | override fun visitTableConstructorExpression(node: TableConstructorExpression, value: StringBuilder) { 323 | value.append("{") 324 | node.fields.forEachIndexed { index, baseASTNode -> 325 | if (index != 0) { 326 | value.append(", ") 327 | } 328 | visitExpressionNode(baseASTNode, value) 329 | } 330 | value.append("}") 331 | } 332 | 333 | override fun visitTableKey(node: TableKey, value: StringBuilder) { 334 | visitExpressionNode(node.key, value) 335 | value.append(" = ") 336 | visitExpressionNode(node.value, value) 337 | } 338 | 339 | override fun visitTableKeyString(node: TableKeyString, value: StringBuilder) { 340 | value.append("[") 341 | visitExpressionNode(node.key, value) 342 | value.append("] = ") 343 | visitExpressionNode(node.value, value) 344 | } 345 | 346 | override fun visitWhenStatement(node: WhenStatement, value: StringBuilder) { 347 | value.append("when ") 348 | visitExpressionNode(node.condition, value) 349 | value.append(" ") 350 | visitStatementNode(node.ifCause, value) 351 | value.append(" ") 352 | node.elseCause?.let { visitStatementNode(it, value) } 353 | } 354 | 355 | override fun visitWhileStatement(node: WhileStatement, value: StringBuilder) { 356 | value.append("while ") 357 | visitExpressionNode(node.condition, value) 358 | value.append(" do") 359 | visitBlockNode(node.body, value) 360 | indent(value) 361 | value.appendLine("end") 362 | } 363 | 364 | override fun visitLocalStatement(node: LocalStatement, value: StringBuilder) { 365 | if (node.init.isNotEmpty()) { 366 | value.append("local ") 367 | node.init.forEachIndexed { index, baseASTNode -> 368 | if (index != 0) { 369 | value.append(", ") 370 | } 371 | visitExpressionNode(baseASTNode, value) 372 | } 373 | } 374 | if (node.variables.isNotEmpty()) { 375 | value.append(" = ") 376 | node.variables.forEachIndexed { index, baseASTNode -> 377 | if (index != 0) { 378 | value.append(", ") 379 | } 380 | visitExpressionNode(baseASTNode, value) 381 | } 382 | } 383 | } 384 | 385 | override fun visitVarargLiteral(node: VarargLiteral, value: StringBuilder) { 386 | value.append("...") 387 | } 388 | 389 | private fun operatorToLua(operator: ExpressionOperator): String { 390 | return when (operator) { 391 | ExpressionOperator.NOT -> "not" 392 | ExpressionOperator.AND -> "and" 393 | ExpressionOperator.OR -> "or" 394 | ExpressionOperator.EQ -> "==" 395 | ExpressionOperator.NE -> "~=" 396 | ExpressionOperator.LT -> "<" 397 | ExpressionOperator.LE -> "<=" 398 | ExpressionOperator.GT -> ">" 399 | ExpressionOperator.GE -> ">=" 400 | ExpressionOperator.ADD -> "+" 401 | ExpressionOperator.MINUS -> "-" 402 | ExpressionOperator.MULT -> "*" 403 | ExpressionOperator.DIV -> "/" 404 | ExpressionOperator.MOD -> "%" 405 | ExpressionOperator.DOUBLE_DIV -> "//" 406 | ExpressionOperator.BIT_AND -> "&" 407 | ExpressionOperator.BIT_OR -> "|" 408 | ExpressionOperator.BIT_TILDE -> "~" 409 | ExpressionOperator.BIT_LT -> "<<" 410 | ExpressionOperator.BIT_GT -> ">>" 411 | ExpressionOperator.BIT_EXP -> "^" 412 | ExpressionOperator.CONCAT -> ".." 413 | ExpressionOperator.GETLEN -> "#" 414 | } 415 | } 416 | 417 | override fun visitAttributeIdentifier(identifier: AttributeIdentifier, value: StringBuilder) { 418 | value.append(identifier.name) 419 | 420 | val attributeName = identifier.attributeName 421 | 422 | if (attributeName != null) { 423 | value.append(" ") 424 | value.append("<") 425 | value.append(attributeName) 426 | value.append(">") 427 | } 428 | } 429 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/util/TrieTree.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.util 2 | 3 | import kotlin.math.abs 4 | import kotlin.math.max 5 | 6 | // https://github.com/Rosemoe/sora-editor/blob/main/editor/src/main/java/io/github/rosemoe/sora/util/TrieTree.java#L30 7 | 8 | 9 | /** 10 | * @author Rose 11 | * TrieTree to query values quickly 12 | */ 13 | class TrieTree { 14 | val root: Node 15 | private var maxLen = 0 16 | 17 | init { 18 | root = Node() 19 | } 20 | 21 | fun put(v: String, token: T) { 22 | maxLen = max(v.length.toDouble(), maxLen.toDouble()).toInt() 23 | addInternal(root, v, 0, v.length, token) 24 | } 25 | 26 | fun put(v: CharSequence, off: Int, len: Int, token: T) { 27 | maxLen = max(maxLen.toDouble(), len.toDouble()).toInt() 28 | addInternal(root, v, off, len, token) 29 | } 30 | 31 | fun get(s: CharSequence, offset: Int, len: Int): T? { 32 | if (len > maxLen) { 33 | return null 34 | } 35 | return getInternal(root, s, offset, len) 36 | } 37 | 38 | private fun getInternal(node: Node, s: CharSequence, offset: Int, len: Int): T? { 39 | if (len == 0) { 40 | return node.token 41 | } 42 | val point = s[offset] 43 | val sub = node.map.get(point) ?: return null 44 | return getInternal(sub, s, offset + 1, len - 1) 45 | } 46 | 47 | private fun addInternal(node: Node, v: CharSequence, i: Int, len: Int, token: T) { 48 | val point = v[i] 49 | var sub = node.map.get(point) 50 | if (sub == null) { 51 | sub = Node() 52 | node.map.put(point, sub) 53 | } 54 | if (len == 1) { 55 | sub.token = token 56 | } else { 57 | addInternal(sub, v, i + 1, len - 1, token) 58 | } 59 | } 60 | 61 | class Node { 62 | val map: HashCharMap> = HashCharMap() 63 | 64 | var token: T? = null 65 | } 66 | 67 | /** 68 | * Hashmap with fixed length 69 | * 70 | * @author Rosemoe 71 | */ 72 | class HashCharMap { 73 | private val columns = arrayOfNulls?>(CAPACITY) 74 | private val ends = arrayOfNulls?>(CAPACITY) 75 | 76 | 77 | fun get(first: Char): V? { 78 | val position = position(first.code) 79 | var pair = columns[position] 80 | while (pair != null) { 81 | if (pair.first == first) { 82 | return pair.second 83 | } 84 | pair = pair.next 85 | } 86 | return null 87 | } 88 | 89 | private fun get(first: Char, position: Int): LinkedPair? { 90 | var pair = columns[position] 91 | while (pair != null) { 92 | if (pair.first == first) { 93 | return pair 94 | } 95 | pair = pair.next 96 | } 97 | return null 98 | } 99 | 100 | fun put(first: Char, second: V) { 101 | val position = position(first.code) 102 | if (ends[position] == null) { 103 | val pair = LinkedPair() 104 | ends[position] = pair 105 | columns[position] = ends[position] 106 | pair.first = first 107 | pair.second = second 108 | return 109 | } 110 | var p = get(first, position) 111 | if (p == null) { 112 | val end = ends[position]!! 113 | end.next = LinkedPair() 114 | p = end.next 115 | ends[position] = p 116 | } 117 | p!!.first = first 118 | p.second = second 119 | } 120 | 121 | 122 | companion object { 123 | private const val CAPACITY = 64 124 | private fun position(first: Int): Int { 125 | return (abs((first xor (first shl 6) * (if ((first and 1) != 0) 3 else 1)).toDouble()) % CAPACITY).toInt() 126 | } 127 | } 128 | } 129 | 130 | 131 | class LinkedPair { 132 | var next: LinkedPair? = null 133 | 134 | var first: Char = 0.toChar() 135 | 136 | var second: V? = null 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/util/atomic.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.util 2 | 3 | fun atomic(value: Int): AtomicInt { 4 | return AtomicInt(value) 5 | } 6 | 7 | class AtomicInt( 8 | private var value: Int 9 | ) { 10 | fun get(): Int { 11 | return value 12 | } 13 | 14 | fun set(newValue: Int) { 15 | value = newValue 16 | } 17 | 18 | fun getAndDecrement(): Int { 19 | val old = value 20 | value -= 1 21 | return old 22 | } 23 | 24 | 25 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/github/dingyi222666/luaparser/util/default.kt: -------------------------------------------------------------------------------- 1 | package io.github.dingyi222666.luaparser.util 2 | 3 | /** 4 | * @author: dingyi 5 | * @date: 2023/2/1 6 | * @description: 7 | **/ 8 | 9 | inline fun T?.requireNotNull(): T { 10 | return requireNotNull(this) 11 | } 12 | 13 | inline fun equalsMore(origin: T, vararg arg: T): Boolean { 14 | return arg.any { it == origin } 15 | } 16 | 17 | 18 | fun parseLuaString(text: String): String { 19 | //char or default 20 | 21 | val buffer = StringBuilder() 22 | var currentIndex = 0 23 | var isLongStringStart = false 24 | var isShortString = false 25 | var isLongStringEnd = false 26 | 27 | while (currentIndex < text.length) { 28 | val currentChar = text[currentIndex] 29 | 30 | if (isShortString && currentChar == '\\') { 31 | val nextChar = text.getOrNull(currentIndex + 1) 32 | if (nextChar == 'n') { 33 | buffer.append('\n') 34 | } else if (nextChar == 't') { 35 | buffer.append('\t') 36 | } else if (nextChar == 'r') { 37 | buffer.append('\r') 38 | } else if (nextChar == 'n') { 39 | buffer.append('\n') 40 | } else if (nextChar == 'f') { 41 | buffer.append('\u000c') 42 | } else if (nextChar == '\\') { 43 | buffer.append('\\') 44 | } else if (nextChar == '\'') { 45 | buffer.append('\'') 46 | } else if (nextChar == '"') { 47 | buffer.append('"') 48 | } else if (nextChar == ']') { 49 | buffer.append(']') 50 | } else if (nextChar == '[') { 51 | buffer.append('[') 52 | } else if (nextChar == '0') { 53 | buffer.append('\u0000') 54 | } else if (nextChar == 'x') { 55 | buffer.append(text.substring(currentIndex + 2, currentIndex + 4).toInt(16).toChar()) 56 | currentIndex += 4 57 | continue 58 | } else if (nextChar == 'd') { 59 | buffer.append(text.substring(currentIndex + 2, currentIndex + 4).toInt(8).toChar()) 60 | currentIndex += 4 61 | continue 62 | } else if (nextChar == 'u') { 63 | val charCode = text.substring(currentIndex + 2, currentIndex + 6).toInt(16) 64 | buffer.append(charCode.toChar()) 65 | currentIndex += 6 66 | continue 67 | } else { 68 | throw IllegalArgumentException("Illegal escape character: $nextChar") 69 | } 70 | 71 | currentIndex += 2 72 | 73 | continue 74 | } 75 | 76 | if (currentChar == '\'' || currentChar == '"') { 77 | if (!isShortString && !isLongStringStart) { 78 | isShortString = true 79 | } else { 80 | break 81 | } 82 | 83 | currentIndex++ 84 | continue 85 | } 86 | 87 | if (currentChar == '[' && !isShortString) { 88 | if (!isLongStringStart && text.getOrNull(currentIndex + 1) == '=') { 89 | isLongStringStart = true 90 | } else if (isLongStringStart && text.getOrNull(currentIndex - 1) == '=') { 91 | isLongStringStart = false 92 | } 93 | 94 | currentIndex++ 95 | continue 96 | } 97 | 98 | if ((currentChar == '=') and !isShortString and (isLongStringStart or isLongStringEnd)) { 99 | currentIndex++ 100 | continue 101 | } 102 | 103 | if (currentChar == ']' && !isShortString) { 104 | if (!isLongStringEnd && text.getOrNull(currentIndex + 1) == '=') { 105 | isLongStringEnd = true 106 | } else if (isLongStringEnd && text.getOrNull(currentIndex - 1) == '=') { 107 | break 108 | } 109 | 110 | currentIndex++ 111 | continue 112 | } 113 | 114 | 115 | 116 | buffer.append(currentChar) 117 | 118 | currentIndex++ 119 | 120 | } 121 | 122 | return buffer.toString() 123 | 124 | } -------------------------------------------------------------------------------- /src/commonTest/kotlin/parser.common.kt: -------------------------------------------------------------------------------- 1 | import io.github.dingyi222666.luaparser.parser.LuaParser 2 | import io.github.dingyi222666.luaparser.parser.LuaVersion 3 | import io.github.dingyi222666.luaparser.source.AST2Lua 4 | import io.github.dingyi222666.luaparser.util.parseLuaString 5 | import kotlin.test.Test 6 | 7 | class ParserTest { 8 | 9 | @Test 10 | fun parse() { 11 | println(parseLuaString("""[==[xxx]==]""")) 12 | println(parseLuaString(""" "xxxx" """)) 13 | println(parseLuaString("""'xxxx'""")) 14 | println(parseLuaString("""'\u6578\x01\n\r\t'""")) 15 | println(parseLuaString("""[[\u6578]]""")) 16 | val parser = LuaParser() 17 | 18 | val root = parser.parse(source) 19 | 20 | println(AST2Lua().asCode(root)) 21 | } 22 | 23 | @Test 24 | fun parseLua54() { 25 | val parser = LuaParser(LuaVersion.LUA_5_4) 26 | 27 | val root = parser.parse(""" 28 | local apple , carrot = 'fruit', 'vegetable' 29 | """.trimIndent()) 30 | 31 | println(AST2Lua().asCode(root)) 32 | } 33 | } 34 | 35 | const val source = """ 36 | local foo = 1 do foo = 2 end 37 | do local foo = 1 end foo = 2 38 | do local foo = 1 end do foo = 2 end 39 | local foo do foo = 1 do foo = 2 end end 40 | local function foo() end foo() 41 | local a = { a } 42 | local b = { b, b.a, b[a], b:a() } 43 | local b = {} local a = { b, b.a, b[a], b:a() } 44 | local c local a = { b[c] } 45 | local a = function() end a() 46 | local a, b = 1, a 47 | local a, b = 1, function() b = 2 end 48 | local a, b for i, a, b in c do end 49 | local a, b, c for i, a, b in c do end 50 | local a = {} function a:b() return self end self = nil 51 | repeat local a = true until a 52 | local a = function (b) end b = 0 53 | -- hello 54 | for a = 1, 5 do end a = 0 55 | """ -------------------------------------------------------------------------------- /src/jsTest/kotlin/parser.js.kt: -------------------------------------------------------------------------------- 1 | import io.github.dingyi222666.luaparser.parser.LuaParser 2 | import io.github.dingyi222666.luaparser.source.AST2Lua 3 | import kotlin.test.Test 4 | 5 | class JsPlatformParserTest { 6 | 7 | @Test 8 | fun parse() { 9 | val parser = LuaParser() 10 | 11 | val root = parser.parse(source) 12 | 13 | console.log(AST2Lua().asCode(root)) 14 | } 15 | } -------------------------------------------------------------------------------- /src/jvmTest/.gitignore: -------------------------------------------------------------------------------- 1 | kotlin/flex/LuaLexer.java 2 | kotlin/flex/LuaTokenTypes.java 3 | kotlin/benchmark.kt 4 | resources/test.lua 5 | /java/flex/LuaLexer.java 6 | /java/flex/LuaTokenTypes.java 7 | -------------------------------------------------------------------------------- /src/jvmTest/kotlin/parser.jvm.kt: -------------------------------------------------------------------------------- 1 | import io.github.dingyi222666.luaparser.lexer.LuaLexer 2 | import io.github.dingyi222666.luaparser.parser.LuaParser 3 | import io.github.dingyi222666.luaparser.parser.ast.node.Position 4 | import io.github.dingyi222666.luaparser.semantic.SemanticAnalyzer 5 | import io.github.dingyi222666.luaparser.semantic.types.FunctionType 6 | import kotlin.test.Test 7 | 8 | class JvmPlatformParserTest { 9 | 10 | @Test 11 | fun parse() { 12 | 13 | 14 | val code = """ 15 | ---@type string 16 | local name = "test" 17 | 18 | ---@type number 19 | local age = 25 20 | 21 | ---@return number 22 | local function add(x, y) 23 | return x + y 24 | end 25 | 26 | local function concat(x, y) 27 | return x .. y 28 | end 29 | 30 | ---@param y number 31 | ---@param x number 32 | ---@return number 33 | local function multiply(x, y) 34 | return x * y 35 | end 36 | 37 | local function pow(x, y) 38 | return multiply(x, y) 39 | end 40 | 41 | local tab = { 42 | name = name, 43 | age = age 44 | } 45 | 46 | local result = add(tab.age, tab.age) 47 | 48 | ---@type string 49 | STRING = 123 -- 这里应该报错,因为类型不匹配 50 | 51 | GLOBAL_VAR = "hello" -- 这是一个全局变量 52 | """.trimIndent() 53 | 54 | val parser = LuaParser() 55 | val analyzer = SemanticAnalyzer() 56 | 57 | val ast = parser.parse(code) 58 | val result = analyzer.analyze(ast) 59 | 60 | // 检查全局变量 61 | val stringType = result.globalSymbols["STRING"]?.type // string 62 | val globalVarType = result.globalSymbols["GLOBAL_VAR"]?.type // string 63 | val addType = result.symbolTable.resolveAtPosition("add", Position(20, 1))?.type 64 | val concatType = result.symbolTable.resolveAtPosition("concat", Position(20, 1))?.type 65 | val multiplyType = result.symbolTable.resolveAtPosition("multiply", Position(20, 1))?.type 66 | 67 | val pairs = result.globalSymbols["ipairs"]?.type 68 | 69 | 70 | println("Global STRING type: $stringType") 71 | println("add type: $addType") 72 | println("concat type: $concatType") 73 | println("Global GLOBAL_VAR type: $globalVarType") 74 | println("multiply type: $multiplyType") 75 | println("ipairs type: $pairs") 76 | 77 | // 检查诊断信息 78 | result.diagnostics.forEach { diagnostic -> 79 | println("${diagnostic.severity}: ${diagnostic.message} at ${diagnostic.range}") 80 | } 81 | } 82 | } 83 | 84 | val testSource = """ 85 | local a = 12 86 | local b = { s = a } 87 | 88 | function b:a() 89 | return { a = b.s } 90 | end 91 | 92 | local function ss() return 1,"" end 93 | 94 | sb,x = ss() 95 | 96 | d = b 97 | 98 | local s = b:a() 99 | 100 | local c = 12 101 | 102 | do 103 | local c = "" 104 | d.c = 1 + "" 105 | end 106 | 107 | function pairs1(t) 108 | return 1, "" 109 | end 110 | 111 | for i = 1, 10 do 112 | print(i) 113 | print(""..2) 114 | print(ss) 115 | print(b) 116 | 117 | end 118 | """.trimIndent() 119 | -------------------------------------------------------------------------------- /src/nativeTest/kotlin/parser.native.kt: -------------------------------------------------------------------------------- 1 | import io.github.dingyi222666.luaparser.parser.LuaParser 2 | import io.github.dingyi222666.luaparser.source.AST2Lua 3 | import kotlin.test.Test 4 | 5 | class NativePlatformParserTest { 6 | 7 | @Test 8 | fun parse() { 9 | val parser = LuaParser() 10 | 11 | val root = parser.parse(source) 12 | 13 | println(AST2Lua().asCode(root)) 14 | } 15 | } 16 | --------------------------------------------------------------------------------