├── .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 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
--------------------------------------------------------------------------------