summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/bin/API-MS-Win-core-xstate-l2-1-0.dllbin0 -> 2560 bytes
-rw-r--r--server/bin/api-ms-win-core-console-l1-1-0.dllbin0 -> 18696 bytes
-rw-r--r--server/bin/api-ms-win-core-datetime-l1-1-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-core-debug-l1-1-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-core-errorhandling-l1-1-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-core-file-l1-1-0.dllbin0 -> 21768 bytes
-rw-r--r--server/bin/api-ms-win-core-file-l1-2-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-core-file-l2-1-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-core-handle-l1-1-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-core-heap-l1-1-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-core-interlocked-l1-1-0.dllbin0 -> 18696 bytes
-rw-r--r--server/bin/api-ms-win-core-libraryloader-l1-1-0.dllbin0 -> 19208 bytes
-rw-r--r--server/bin/api-ms-win-core-localization-l1-2-0.dllbin0 -> 20744 bytes
-rw-r--r--server/bin/api-ms-win-core-memory-l1-1-0.dllbin0 -> 18696 bytes
-rw-r--r--server/bin/api-ms-win-core-namedpipe-l1-1-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-core-processenvironment-l1-1-0.dllbin0 -> 19208 bytes
-rw-r--r--server/bin/api-ms-win-core-processthreads-l1-1-0.dllbin0 -> 20232 bytes
-rw-r--r--server/bin/api-ms-win-core-processthreads-l1-1-1.dllbin0 -> 18696 bytes
-rw-r--r--server/bin/api-ms-win-core-profile-l1-1-0.dllbin0 -> 17672 bytes
-rw-r--r--server/bin/api-ms-win-core-rtlsupport-l1-1-0.dllbin0 -> 17672 bytes
-rw-r--r--server/bin/api-ms-win-core-string-l1-1-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-core-synch-l1-1-0.dllbin0 -> 20232 bytes
-rw-r--r--server/bin/api-ms-win-core-synch-l1-2-0.dllbin0 -> 18696 bytes
-rw-r--r--server/bin/api-ms-win-core-sysinfo-l1-1-0.dllbin0 -> 19208 bytes
-rw-r--r--server/bin/api-ms-win-core-timezone-l1-1-0.dllbin0 -> 18696 bytes
-rw-r--r--server/bin/api-ms-win-core-util-l1-1-0.dllbin0 -> 18184 bytes
-rw-r--r--server/bin/api-ms-win-crt-conio-l1-1-0.dllbin0 -> 19208 bytes
-rw-r--r--server/bin/api-ms-win-crt-convert-l1-1-0.dllbin0 -> 22280 bytes
-rw-r--r--server/bin/api-ms-win-crt-environment-l1-1-0.dllbin0 -> 18696 bytes
-rw-r--r--server/bin/api-ms-win-crt-filesystem-l1-1-0.dllbin0 -> 20232 bytes
-rw-r--r--server/bin/api-ms-win-crt-heap-l1-1-0.dllbin0 -> 19208 bytes
-rw-r--r--server/bin/api-ms-win-crt-locale-l1-1-0.dllbin0 -> 18696 bytes
-rw-r--r--server/bin/api-ms-win-crt-math-l1-1-0.dllbin0 -> 28936 bytes
-rw-r--r--server/bin/api-ms-win-crt-multibyte-l1-1-0.dllbin0 -> 26376 bytes
-rw-r--r--server/bin/api-ms-win-crt-private-l1-1-0.dllbin0 -> 72968 bytes
-rw-r--r--server/bin/api-ms-win-crt-process-l1-1-0.dllbin0 -> 19208 bytes
-rw-r--r--server/bin/api-ms-win-crt-runtime-l1-1-0.dllbin0 -> 22792 bytes
-rw-r--r--server/bin/api-ms-win-crt-stdio-l1-1-0.dllbin0 -> 24328 bytes
-rw-r--r--server/bin/api-ms-win-crt-string-l1-1-0.dllbin0 -> 24328 bytes
-rw-r--r--server/bin/api-ms-win-crt-time-l1-1-0.dllbin0 -> 20744 bytes
-rw-r--r--server/bin/api-ms-win-crt-utility-l1-1-0.dllbin0 -> 18696 bytes
-rw-r--r--server/bin/debugger.dllbin0 -> 406528 bytes
-rw-r--r--server/bin/ffi.dllbin0 -> 104960 bytes
-rw-r--r--server/bin/filesystem.dllbin0 -> 57856 bytes
-rw-r--r--server/bin/lpeglabel.dllbin0 -> 38400 bytes
-rw-r--r--server/bin/lua.exebin0 -> 34304 bytes
-rw-r--r--server/bin/lua53.dllbin0 -> 235520 bytes
-rw-r--r--server/bin/msvcp140.dllbin0 -> 462120 bytes
-rw-r--r--server/bin/process.dllbin0 -> 15872 bytes
-rw-r--r--server/bin/vcruntime140.dllbin0 -> 87352 bytes
-rw-r--r--server/bin/ydbase.dllbin0 -> 175616 bytes
-rw-r--r--server/bin/yue.dllbin0 -> 785408 bytes
-rw-r--r--server/main.lua19
-rw-r--r--server/src/ffi/sleep.lua8
-rw-r--r--server/src/ffi/unicode.lua49
-rw-r--r--server/src/global_protect.lua10
-rw-r--r--server/src/json/decode.lua128
-rw-r--r--server/src/json/encode.lua135
-rw-r--r--server/src/json/init.lua6
-rw-r--r--server/src/log.lua89
-rw-r--r--server/src/lsp.lua155
-rw-r--r--server/src/matcher/definition.lua282
-rw-r--r--server/src/matcher/implementation.lua377
-rw-r--r--server/src/matcher/init.lua6
-rw-r--r--server/src/method/exit.lua3
-rw-r--r--server/src/method/init.lua17
-rw-r--r--server/src/method/initialize.lua18
-rw-r--r--server/src/method/initialized.lua3
-rw-r--r--server/src/method/shutdown.lua3
-rw-r--r--server/src/method/textDocument/definition.lua46
-rw-r--r--server/src/method/textDocument/didChange.lua7
-rw-r--r--server/src/method/textDocument/didClose.lua5
-rw-r--r--server/src/method/textDocument/didOpen.lua5
-rw-r--r--server/src/method/textDocument/implementation.lua51
-rw-r--r--server/src/parser/calcline.lua93
-rw-r--r--server/src/parser/grammar.lua336
-rw-r--r--server/src/parser/init.lua7
-rw-r--r--server/src/parser/relabel.lua364
-rw-r--r--server/src/parser/split.lua9
-rw-r--r--server/src/service.lua52
-rw-r--r--server/src/utility/init.lua2
-rw-r--r--server/src/utility/io.lua21
-rw-r--r--server/src/utility/table.lua62
-rw-r--r--server/test/definition/arg.lua23
-rw-r--r--server/test/definition/bug.lua15
-rw-r--r--server/test/definition/function.lua24
-rw-r--r--server/test/definition/init.lua22
-rw-r--r--server/test/definition/local.lua191
-rw-r--r--server/test/definition/set.lua30
-rw-r--r--server/test/definition/table.lua6
-rw-r--r--server/test/implementation/arg.lua23
-rw-r--r--server/test/implementation/bug.lua15
-rw-r--r--server/test/implementation/function.lua24
-rw-r--r--server/test/implementation/if.lua106
-rw-r--r--server/test/implementation/init.lua57
-rw-r--r--server/test/implementation/local.lua191
-rw-r--r--server/test/implementation/set.lua31
-rw-r--r--server/test/implementation/table.lua6
-rw-r--r--server/test/main.lua31
99 files changed, 3163 insertions, 0 deletions
diff --git a/server/bin/API-MS-Win-core-xstate-l2-1-0.dll b/server/bin/API-MS-Win-core-xstate-l2-1-0.dll
new file mode 100644
index 00000000..064d9c7f
--- /dev/null
+++ b/server/bin/API-MS-Win-core-xstate-l2-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-console-l1-1-0.dll b/server/bin/api-ms-win-core-console-l1-1-0.dll
new file mode 100644
index 00000000..d991b177
--- /dev/null
+++ b/server/bin/api-ms-win-core-console-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-datetime-l1-1-0.dll b/server/bin/api-ms-win-core-datetime-l1-1-0.dll
new file mode 100644
index 00000000..eb960250
--- /dev/null
+++ b/server/bin/api-ms-win-core-datetime-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-debug-l1-1-0.dll b/server/bin/api-ms-win-core-debug-l1-1-0.dll
new file mode 100644
index 00000000..1f84ace6
--- /dev/null
+++ b/server/bin/api-ms-win-core-debug-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-errorhandling-l1-1-0.dll b/server/bin/api-ms-win-core-errorhandling-l1-1-0.dll
new file mode 100644
index 00000000..07b393ef
--- /dev/null
+++ b/server/bin/api-ms-win-core-errorhandling-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-file-l1-1-0.dll b/server/bin/api-ms-win-core-file-l1-1-0.dll
new file mode 100644
index 00000000..2ad839ff
--- /dev/null
+++ b/server/bin/api-ms-win-core-file-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-file-l1-2-0.dll b/server/bin/api-ms-win-core-file-l1-2-0.dll
new file mode 100644
index 00000000..ca52643a
--- /dev/null
+++ b/server/bin/api-ms-win-core-file-l1-2-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-file-l2-1-0.dll b/server/bin/api-ms-win-core-file-l2-1-0.dll
new file mode 100644
index 00000000..b9798e0e
--- /dev/null
+++ b/server/bin/api-ms-win-core-file-l2-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-handle-l1-1-0.dll b/server/bin/api-ms-win-core-handle-l1-1-0.dll
new file mode 100644
index 00000000..1fabaeee
--- /dev/null
+++ b/server/bin/api-ms-win-core-handle-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-heap-l1-1-0.dll b/server/bin/api-ms-win-core-heap-l1-1-0.dll
new file mode 100644
index 00000000..0c78d9b6
--- /dev/null
+++ b/server/bin/api-ms-win-core-heap-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-interlocked-l1-1-0.dll b/server/bin/api-ms-win-core-interlocked-l1-1-0.dll
new file mode 100644
index 00000000..f30ea8b3
--- /dev/null
+++ b/server/bin/api-ms-win-core-interlocked-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-libraryloader-l1-1-0.dll b/server/bin/api-ms-win-core-libraryloader-l1-1-0.dll
new file mode 100644
index 00000000..346770fc
--- /dev/null
+++ b/server/bin/api-ms-win-core-libraryloader-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-localization-l1-2-0.dll b/server/bin/api-ms-win-core-localization-l1-2-0.dll
new file mode 100644
index 00000000..1e5072f4
--- /dev/null
+++ b/server/bin/api-ms-win-core-localization-l1-2-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-memory-l1-1-0.dll b/server/bin/api-ms-win-core-memory-l1-1-0.dll
new file mode 100644
index 00000000..ca54325f
--- /dev/null
+++ b/server/bin/api-ms-win-core-memory-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-namedpipe-l1-1-0.dll b/server/bin/api-ms-win-core-namedpipe-l1-1-0.dll
new file mode 100644
index 00000000..0338d576
--- /dev/null
+++ b/server/bin/api-ms-win-core-namedpipe-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-processenvironment-l1-1-0.dll b/server/bin/api-ms-win-core-processenvironment-l1-1-0.dll
new file mode 100644
index 00000000..da33c1a3
--- /dev/null
+++ b/server/bin/api-ms-win-core-processenvironment-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-processthreads-l1-1-0.dll b/server/bin/api-ms-win-core-processthreads-l1-1-0.dll
new file mode 100644
index 00000000..0baacbba
--- /dev/null
+++ b/server/bin/api-ms-win-core-processthreads-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-processthreads-l1-1-1.dll b/server/bin/api-ms-win-core-processthreads-l1-1-1.dll
new file mode 100644
index 00000000..cdd9a887
--- /dev/null
+++ b/server/bin/api-ms-win-core-processthreads-l1-1-1.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-profile-l1-1-0.dll b/server/bin/api-ms-win-core-profile-l1-1-0.dll
new file mode 100644
index 00000000..5fcfd92c
--- /dev/null
+++ b/server/bin/api-ms-win-core-profile-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-rtlsupport-l1-1-0.dll b/server/bin/api-ms-win-core-rtlsupport-l1-1-0.dll
new file mode 100644
index 00000000..2735c669
--- /dev/null
+++ b/server/bin/api-ms-win-core-rtlsupport-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-string-l1-1-0.dll b/server/bin/api-ms-win-core-string-l1-1-0.dll
new file mode 100644
index 00000000..2ab518ad
--- /dev/null
+++ b/server/bin/api-ms-win-core-string-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-synch-l1-1-0.dll b/server/bin/api-ms-win-core-synch-l1-1-0.dll
new file mode 100644
index 00000000..56447809
--- /dev/null
+++ b/server/bin/api-ms-win-core-synch-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-synch-l1-2-0.dll b/server/bin/api-ms-win-core-synch-l1-2-0.dll
new file mode 100644
index 00000000..0d22398b
--- /dev/null
+++ b/server/bin/api-ms-win-core-synch-l1-2-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-sysinfo-l1-1-0.dll b/server/bin/api-ms-win-core-sysinfo-l1-1-0.dll
new file mode 100644
index 00000000..2fcbd871
--- /dev/null
+++ b/server/bin/api-ms-win-core-sysinfo-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-timezone-l1-1-0.dll b/server/bin/api-ms-win-core-timezone-l1-1-0.dll
new file mode 100644
index 00000000..37492726
--- /dev/null
+++ b/server/bin/api-ms-win-core-timezone-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-core-util-l1-1-0.dll b/server/bin/api-ms-win-core-util-l1-1-0.dll
new file mode 100644
index 00000000..536f9c97
--- /dev/null
+++ b/server/bin/api-ms-win-core-util-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-conio-l1-1-0.dll b/server/bin/api-ms-win-crt-conio-l1-1-0.dll
new file mode 100644
index 00000000..a5312bd0
--- /dev/null
+++ b/server/bin/api-ms-win-crt-conio-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-convert-l1-1-0.dll b/server/bin/api-ms-win-crt-convert-l1-1-0.dll
new file mode 100644
index 00000000..d0e8a74f
--- /dev/null
+++ b/server/bin/api-ms-win-crt-convert-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-environment-l1-1-0.dll b/server/bin/api-ms-win-crt-environment-l1-1-0.dll
new file mode 100644
index 00000000..33ab4235
--- /dev/null
+++ b/server/bin/api-ms-win-crt-environment-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-filesystem-l1-1-0.dll b/server/bin/api-ms-win-crt-filesystem-l1-1-0.dll
new file mode 100644
index 00000000..73c1bbd0
--- /dev/null
+++ b/server/bin/api-ms-win-crt-filesystem-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-heap-l1-1-0.dll b/server/bin/api-ms-win-crt-heap-l1-1-0.dll
new file mode 100644
index 00000000..b2491c58
--- /dev/null
+++ b/server/bin/api-ms-win-crt-heap-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-locale-l1-1-0.dll b/server/bin/api-ms-win-crt-locale-l1-1-0.dll
new file mode 100644
index 00000000..5c6daf8b
--- /dev/null
+++ b/server/bin/api-ms-win-crt-locale-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-math-l1-1-0.dll b/server/bin/api-ms-win-crt-math-l1-1-0.dll
new file mode 100644
index 00000000..59f5938e
--- /dev/null
+++ b/server/bin/api-ms-win-crt-math-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-multibyte-l1-1-0.dll b/server/bin/api-ms-win-crt-multibyte-l1-1-0.dll
new file mode 100644
index 00000000..08fde912
--- /dev/null
+++ b/server/bin/api-ms-win-crt-multibyte-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-private-l1-1-0.dll b/server/bin/api-ms-win-crt-private-l1-1-0.dll
new file mode 100644
index 00000000..5504b29b
--- /dev/null
+++ b/server/bin/api-ms-win-crt-private-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-process-l1-1-0.dll b/server/bin/api-ms-win-crt-process-l1-1-0.dll
new file mode 100644
index 00000000..6db6d131
--- /dev/null
+++ b/server/bin/api-ms-win-crt-process-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-runtime-l1-1-0.dll b/server/bin/api-ms-win-crt-runtime-l1-1-0.dll
new file mode 100644
index 00000000..3ccdf40b
--- /dev/null
+++ b/server/bin/api-ms-win-crt-runtime-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-stdio-l1-1-0.dll b/server/bin/api-ms-win-crt-stdio-l1-1-0.dll
new file mode 100644
index 00000000..50e08763
--- /dev/null
+++ b/server/bin/api-ms-win-crt-stdio-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-string-l1-1-0.dll b/server/bin/api-ms-win-crt-string-l1-1-0.dll
new file mode 100644
index 00000000..32a56dbb
--- /dev/null
+++ b/server/bin/api-ms-win-crt-string-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-time-l1-1-0.dll b/server/bin/api-ms-win-crt-time-l1-1-0.dll
new file mode 100644
index 00000000..f5c85a6c
--- /dev/null
+++ b/server/bin/api-ms-win-crt-time-l1-1-0.dll
Binary files differ
diff --git a/server/bin/api-ms-win-crt-utility-l1-1-0.dll b/server/bin/api-ms-win-crt-utility-l1-1-0.dll
new file mode 100644
index 00000000..0dd8c7e9
--- /dev/null
+++ b/server/bin/api-ms-win-crt-utility-l1-1-0.dll
Binary files differ
diff --git a/server/bin/debugger.dll b/server/bin/debugger.dll
new file mode 100644
index 00000000..5dfd8be4
--- /dev/null
+++ b/server/bin/debugger.dll
Binary files differ
diff --git a/server/bin/ffi.dll b/server/bin/ffi.dll
new file mode 100644
index 00000000..048b2b0f
--- /dev/null
+++ b/server/bin/ffi.dll
Binary files differ
diff --git a/server/bin/filesystem.dll b/server/bin/filesystem.dll
new file mode 100644
index 00000000..536b3d93
--- /dev/null
+++ b/server/bin/filesystem.dll
Binary files differ
diff --git a/server/bin/lpeglabel.dll b/server/bin/lpeglabel.dll
new file mode 100644
index 00000000..56b76ed4
--- /dev/null
+++ b/server/bin/lpeglabel.dll
Binary files differ
diff --git a/server/bin/lua.exe b/server/bin/lua.exe
new file mode 100644
index 00000000..f72d0632
--- /dev/null
+++ b/server/bin/lua.exe
Binary files differ
diff --git a/server/bin/lua53.dll b/server/bin/lua53.dll
new file mode 100644
index 00000000..a8d1f825
--- /dev/null
+++ b/server/bin/lua53.dll
Binary files differ
diff --git a/server/bin/msvcp140.dll b/server/bin/msvcp140.dll
new file mode 100644
index 00000000..0a30fd08
--- /dev/null
+++ b/server/bin/msvcp140.dll
Binary files differ
diff --git a/server/bin/process.dll b/server/bin/process.dll
new file mode 100644
index 00000000..ff0fc1d3
--- /dev/null
+++ b/server/bin/process.dll
Binary files differ
diff --git a/server/bin/vcruntime140.dll b/server/bin/vcruntime140.dll
new file mode 100644
index 00000000..fa755e5c
--- /dev/null
+++ b/server/bin/vcruntime140.dll
Binary files differ
diff --git a/server/bin/ydbase.dll b/server/bin/ydbase.dll
new file mode 100644
index 00000000..48fa2abf
--- /dev/null
+++ b/server/bin/ydbase.dll
Binary files differ
diff --git a/server/bin/yue.dll b/server/bin/yue.dll
new file mode 100644
index 00000000..42f015c6
--- /dev/null
+++ b/server/bin/yue.dll
Binary files differ
diff --git a/server/main.lua b/server/main.lua
new file mode 100644
index 00000000..773fb0a8
--- /dev/null
+++ b/server/main.lua
@@ -0,0 +1,19 @@
+require 'filesystem'
+ROOT = fs.current_path()
+package.path = (ROOT / 'src' / '?.lua'):string()
+ .. ';' .. (ROOT / 'src' / '?' / 'init.lua'):string()
+
+log = require 'log'
+log.init(ROOT, ROOT / 'log' / 'test.log')
+log.info('Lua 语言服务启动,路径为:', ROOT)
+
+local dbg = require 'debugger'
+dbg:io 'listen:0.0.0.0:546858'
+dbg:start()
+
+require 'utility'
+require 'global_protect'
+local service = require 'service'
+local session = service()
+
+session:listen()
diff --git a/server/src/ffi/sleep.lua b/server/src/ffi/sleep.lua
new file mode 100644
index 00000000..5c4be639
--- /dev/null
+++ b/server/src/ffi/sleep.lua
@@ -0,0 +1,8 @@
+local ffi = require 'ffi'
+ffi.cdef[[
+ void Sleep(unsigned long dwMilliseconds);
+]]
+
+return function (time)
+ ffi.C.Sleep(time)
+end
diff --git a/server/src/ffi/unicode.lua b/server/src/ffi/unicode.lua
new file mode 100644
index 00000000..734b4679
--- /dev/null
+++ b/server/src/ffi/unicode.lua
@@ -0,0 +1,49 @@
+local ffi = require 'ffi'
+ffi.cdef[[
+ int MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char* lpMultiByteStr, int cbMultiByte, wchar_t* lpWideCharStr, int cchWideChar);
+ int WideCharToMultiByte(unsigned int CodePage, unsigned long dwFlags, const wchar_t* lpWideCharStr, int cchWideChar, char* lpMultiByteStr, int cchMultiByte, const char* lpDefaultChar, int* pfUsedDefaultChar);
+]]
+
+local CP_UTF8 = 65001
+local CP_ACP = 0
+
+local function u2w(input)
+ local wlen = ffi.C.MultiByteToWideChar(CP_UTF8, 0, input, #input, nil, 0)
+ local wstr = ffi.new('wchar_t[?]', wlen+1)
+ ffi.C.MultiByteToWideChar(CP_UTF8, 0, input, #input, wstr, wlen)
+ return wstr, wlen
+end
+
+local function a2w(input)
+ local wlen = ffi.C.MultiByteToWideChar(CP_ACP, 0, input, #input, nil, 0)
+ local wstr = ffi.new('wchar_t[?]', wlen+1)
+ ffi.C.MultiByteToWideChar(CP_ACP, 0, input, #input, wstr, wlen)
+ return wstr, wlen
+end
+
+local function w2u(wstr, wlen)
+ local len = ffi.C.WideCharToMultiByte(CP_UTF8, 0, wstr, wlen, nil, 0, nil, nil)
+ local str = ffi.new('char[?]', len+1)
+ ffi.C.WideCharToMultiByte(CP_UTF8, 0, wstr, wlen, str, len, nil, nil)
+ return ffi.string(str)
+end
+
+local function w2a(wstr, wlen)
+ local len = ffi.C.WideCharToMultiByte(CP_ACP, 0, wstr, wlen, nil, 0, nil, nil)
+ local str = ffi.new('char[?]', len)
+ ffi.C.WideCharToMultiByte(CP_ACP, 0, wstr, wlen, str, len, nil, nil)
+ return ffi.string(str)
+end
+
+return {
+ u2w = u2w,
+ a2w = a2w,
+ w2u = w2u,
+ w2a = w2a,
+ u2a = function (input)
+ return w2a(u2w(input))
+ end,
+ a2u = function (input)
+ return w2u(a2w(input))
+ end,
+}
diff --git a/server/src/global_protect.lua b/server/src/global_protect.lua
new file mode 100644
index 00000000..6c736ea6
--- /dev/null
+++ b/server/src/global_protect.lua
@@ -0,0 +1,10 @@
+local mt = {}
+setmetatable(_G, mt)
+
+function mt:__index(k)
+ error(('读取不存在的全局变量[%s]'):format(k), 2)
+end
+
+function mt:__newindex(k, v)
+ error(('保存全局变量[%s] = [%s]'):format(k, v), 2)
+end
diff --git a/server/src/json/decode.lua b/server/src/json/decode.lua
new file mode 100644
index 00000000..6a93d127
--- /dev/null
+++ b/server/src/json/decode.lua
@@ -0,0 +1,128 @@
+local lpeg = require 'lpeglabel'
+local save_sort
+local table_pack = table.pack
+
+local P = lpeg.P
+local S = lpeg.S
+local R = lpeg.R
+local V = lpeg.V
+local C = lpeg.C
+local Ct = lpeg.Ct
+local Cg = lpeg.Cg
+local Cc = lpeg.Cc
+local Cp = lpeg.Cp
+local Cs = lpeg.Cs
+
+local EscMap = {
+ ['t'] = '\t',
+ ['r'] = '\r',
+ ['n'] = '\n',
+ ['"'] = '"',
+ ['\\'] = '\\',
+}
+local BoolMap = {
+ ['true'] = true,
+ ['false'] = false,
+}
+
+local hashmt = {
+ __pairs = function (self)
+ local i = 1
+ local function next()
+ i = i + 1
+ local k = self[i]
+ if k == nil then
+ return
+ end
+ local v = self[k]
+ if v == nil then
+ return next()
+ end
+ return k, v
+ end
+ return next
+ end,
+ __newindex = function (self, k, v)
+ local i = 2
+ while self[i] do
+ i = i + 1
+ end
+ rawset(self, i, k)
+ rawset(self, k, v)
+ end,
+ __debugger_extand = function (self)
+ local list = {}
+ for k, v in pairs(self) do
+ k = tostring(k)
+ list[#list+1] = k
+ list[k] = v
+ end
+ return list
+ end,
+}
+
+local tointeger = math.tointeger
+local tonumber = tonumber
+local setmetatable = setmetatable
+local rawset = rawset
+local function HashTable(patt)
+ return C(patt) / function (_, ...)
+ local hash = table_pack(...)
+ local n = hash.n
+ hash.n = nil
+ if save_sort then
+ local max = n // 2
+ for i = 1, max do
+ local key, value = hash[2*i-1], hash[2*i]
+ hash[key] = value
+ hash[i+1] = key
+ end
+ hash[1] = nil
+ for i = max+2, max*2 do
+ hash[i] = nil
+ end
+ return setmetatable(hash, hashmt)
+ else
+ local max = n // 2
+ for i = 1, max do
+ local a = 2*i-1
+ local b = 2*i
+ local key, value = hash[a], hash[b]
+ hash[key] = value
+ hash[a] = nil
+ hash[b] = nil
+ end
+ return hash
+ end
+ end
+end
+
+local Token = P
+{
+ V'Value' * Cp(),
+ Nl = P'\r\n' + S'\r\n',
+ Sp = S' \t',
+ Spnl = (V'Sp' + V'Nl')^0,
+ Bool = C(P'true' + P'false') / BoolMap,
+ Int = C('0' + (P'-'^-1 * R'19' * R'09'^0)) / tointeger,
+ Float = C(P'-'^-1 * ('0' + R'19' * R'09'^0) * '.' * R'09'^0) / tonumber,
+ Null = P'null' * Cc(nil),
+ String = '"' * Cs(V'Char'^0) * '"',
+ Char = V'Esc' + (1 - P'"' - P'\t' - V'Nl'),
+ Esc = P'\\' * C(S'tnr"\\') / EscMap,
+ Hash = V'Spnl' * '{' * V'Spnl' * HashTable(V'Object'^-1 * (P',' * V'Object')^0) * V'Spnl' * P'}' * V'Spnl',
+ Array = V'Spnl' * '[' * V'Spnl' * Ct(V'Value'^-1 * (P',' * V'Spnl' * V'Value')^0) * V'Spnl' * P']' * V'Spnl',
+ Object = V'Spnl' * V'Key' * V'Spnl' * V'Value' * V'Spnl',
+ Key = V'String' * V'Spnl' * ':',
+ Value = V'Hash' + V'Array' + V'Bool' + V'Null' + V'String' + V'Float' + V'Int',
+}
+
+return function (str, save_sort_)
+ save_sort = save_sort_
+ local table, pos = Token:match(str)
+ if not pos or pos <= #str then
+ pos = pos or 1
+ error(('没匹配完[%s]\n%s'):format(pos, str:sub(pos, pos+100)))
+ end
+ return table
+end
diff --git a/server/src/json/encode.lua b/server/src/json/encode.lua
new file mode 100644
index 00000000..4bba26de
--- /dev/null
+++ b/server/src/json/encode.lua
@@ -0,0 +1,135 @@
+
+local rep = string.rep
+local format = string.format
+local gsub = string.gsub
+local sub = string.sub
+local sort = table.sort
+local find = string.find
+local tostring = tostring
+local getmetatable = debug.getmetatable
+local type = type
+local next = next
+local ipairs = ipairs
+
+local index
+local lines
+local n = -1
+local tabs = {}
+
+local esc_map = {
+ ['\\'] = '\\\\',
+ ['\r'] = '\\r',
+ ['\n'] = '\\n',
+ ['\t'] = '\\t',
+ ['"'] = '\\"',
+}
+
+local function encode(data, key)
+ n = n + 1
+ if not tabs[n] then
+ tabs[n] = rep('\t', n)
+ end
+ local tp = type(data)
+ if tp == 'table' then
+ if not data[1] and next(data) then
+ -- 认为这个是哈希表
+ if key then
+ index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : {\n'
+ else
+ index=index+1;lines[index] = tabs[n] .. '{\n'
+ end
+ local meta = getmetatable(data)
+ local sep
+ if meta and meta.__pairs then
+ for k, v in meta.__pairs(data), data do
+ if encode(v, k) then
+ index=index+1;lines[index] = ',\n'
+ sep = true
+ end
+ end
+ else
+ local list = {}
+ local i = 0
+ for k in next, data do
+ i=i+1;list[i] = k
+ end
+ sort(list)
+ for j = 1, i do
+ local k = list[j]
+ if encode(data[k], k) then
+ index=index+1;lines[index] = ',\n'
+ sep = true
+ end
+ end
+ end
+ if sep then
+ lines[index] = '\n'
+ end
+ index=index+1;lines[index] = tabs[n] .. '}'
+ else
+ -- 认为这个是数组
+ if key then
+ index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : [\n'
+ else
+ index=index+1;lines[index] = tabs[n] .. '[\n'
+ end
+ local sep
+ for k, v in pairs(data) do
+ if encode(v) then
+ index=index+1;lines[index] = ',\n'
+ sep = true
+ end
+ end
+ if sep then
+ lines[index] = '\n'
+ end
+ index=index+1;lines[index] = tabs[n] .. ']'
+ end
+ elseif tp == 'number' then
+ data = tostring(data)
+ -- 判断 inf -inf -nan(ind) 1.#INF -1.#INF -1.#IND
+ if find(data, '%a') then
+ data = '0'
+ end
+ if key then
+ index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : ' .. data
+ else
+ index=index+1;lines[index] = tabs[n] .. data
+ end
+ elseif tp == 'boolean' then
+ if key then
+ index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : ' .. tostring(data)
+ else
+ index=index+1;lines[index] = tabs[n] .. tostring(data)
+ end
+ elseif tp == 'nil' then
+ if key then
+ index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : null'
+ else
+ index=index+1;lines[index] = tabs[n] .. 'null'
+ end
+ elseif tp == 'string' then
+ local str = gsub(data, '[\\\r\n\t"]', esc_map)
+ if key then
+ index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : "' .. str .. '"'
+ else
+ index=index+1;lines[index] = tabs[n] .. '"' .. str .. '"'
+ end
+ else
+ n = n - 1
+ return false
+ end
+ n = n - 1
+ return true
+end
+
+local function json(t)
+ lines = {}
+ index = 0
+
+ encode(t)
+
+ return table.concat(lines)
+end
+
+return json
diff --git a/server/src/json/init.lua b/server/src/json/init.lua
new file mode 100644
index 00000000..c28e7aed
--- /dev/null
+++ b/server/src/json/init.lua
@@ -0,0 +1,6 @@
+local api = {
+ decode = require 'json.decode',
+ encode = require 'json.encode',
+}
+
+return api
diff --git a/server/src/log.lua b/server/src/log.lua
new file mode 100644
index 00000000..13e3ce7c
--- /dev/null
+++ b/server/src/log.lua
@@ -0,0 +1,89 @@
+require 'filesystem'
+
+local log = {}
+
+log.file = nil
+log.start_time = os.time() - os.clock()
+log.size = 0
+log.max_size = 100 * 1024 * 1024
+
+local function trim_src(src)
+ src = src:sub(log.prefix_len, -5)
+ src = src:gsub('[\\/]+', '.')
+ return src
+end
+
+local function push_log(level, ...)
+ if not log.path then
+ return
+ end
+ if log.size > log.max_size then
+ return
+ end
+ local t = table.pack(...)
+ for i = 1, t.n do
+ t[i] = tostring(t[i])
+ end
+ local str = table.concat(t, '\t')
+ if level == 'error' then
+ str = str .. '\n' .. debug.traceback(nil, 3)
+ end
+ if not log.file then
+ log.file = io.open(log.path, 'w')
+ if not log.file then
+ return
+ end
+ log.file:write('')
+ log.file:close()
+ log.file = io.open(log.path, 'ab')
+ if not log.file then
+ return
+ end
+ log.file:setvbuf 'no'
+ end
+ local sec, ms = math.modf(log.start_time + os.clock())
+ local timestr = os.date('%Y-%m-%d %H:%M:%S', sec)
+ local info = debug.getinfo(3, 'Sl')
+ local buf
+ if info and info.currentline > 0 then
+ buf = ('[%s.%03.f][%s]: [%s:%s]%s\n'):format(timestr, ms * 1000, level, trim_src(info.source), info.currentline, str)
+ else
+ buf = ('[%s.%03.f][%s]: %s\n'):format(timestr, ms * 1000, level, str)
+ end
+ log.file:write(buf)
+ log.size = log.size + #buf
+ if log.size > log.max_size then
+ log.file:write('[日志过大]')
+ end
+ return str
+end
+
+function log.info(...)
+ push_log('info', ...)
+end
+
+function log.debug(...)
+ push_log('debug', ...)
+end
+
+function log.trace(...)
+ push_log('trace', ...)
+end
+
+function log.warn(...)
+ push_log('warn', ...)
+end
+
+function log.error(...)
+ push_log('error', ...)
+end
+
+function log.init(root, path)
+ log.path = path:string()
+ log.prefix_len = #root:string() + 3
+ if not fs.exists(path:parent_path()) then
+ fs.create_directories(path:parent_path())
+ end
+end
+
+return log
diff --git a/server/src/lsp.lua b/server/src/lsp.lua
new file mode 100644
index 00000000..4a88aba6
--- /dev/null
+++ b/server/src/lsp.lua
@@ -0,0 +1,155 @@
+local json = require 'json'
+local parser = require 'parser'
+
+local ErrorCodes = {
+ -- Defined by JSON RPC
+ ParseError = -32700,
+ InvalidRequest = -32600,
+ MethodNotFound = -32601,
+ InvalidParams = -32602,
+ InternalError = -32603,
+ serverErrorStart = -32099,
+ serverErrorEnd = -32000,
+ ServerNotInitialized = -32002,
+ UnknownErrorCode = -32001,
+
+ -- Defined by the protocol.
+ RequestCancelled = -32800,
+}
+
+local mt = {}
+mt.__index = mt
+
+mt._input = nil
+mt._output = nil
+mt._method = nil
+mt._file = nil
+
+function mt:_callback(method, params)
+ local f = self._method
+ if f then
+ return f(method, params)
+ end
+ return nil, '没有注册method'
+end
+
+function mt:_send(data)
+ local f = self._output
+ if not f then
+ return
+ end
+ data.jsonrpc = '2.0'
+ local content = json.encode(data)
+ local buf = ('Content-Length: %d\r\n\r\n%s'):format(#content, content)
+ f(buf)
+end
+
+function mt:_readAsContent(header)
+ local len = tonumber(header:match('%d+'))
+ if not len then
+ log.error('错误的协议头:', header)
+ return
+ end
+ local buf = self:read(len+2)
+ local suc, res = pcall(json.decode, buf)
+ if not suc then
+ log.error('错误的协议:', buf)
+ return
+ end
+ local id = res.id
+ local method = res.method
+ local params = res.params
+ local response, err = self:_callback(method, params)
+ if id then
+ if response then
+ self:_send {
+ id = id,
+ result = response,
+ }
+ else
+ self:_send {
+ id = id,
+ error = {
+ code = ErrorCodes.UnknownErrorCode,
+ message = err or ('没有回应:' .. method),
+ },
+ }
+ end
+ end
+ if not response then
+ log.error(err or ('没有回应:' .. method))
+ end
+ -- 运行时不清理垃圾,在回复前端之后清理垃圾
+ collectgarbage()
+end
+
+function mt:setInput(input)
+ self._input = input
+end
+
+function mt:setOutput(output)
+ self._output = output
+end
+
+function mt:read(mode)
+ if not self._input then
+ return nil
+ end
+ return self._input(mode)
+end
+
+function mt:saveText(uri, version, text)
+ local obj = self._file[uri]
+ if obj then
+ if obj.version >= version then
+ return
+ end
+ obj.version = version
+ obj.text = text
+ else
+ self._file[uri] = {
+ version = version,
+ text = text,
+ }
+ end
+end
+
+function mt:loadText(uri)
+ local obj = self._file[uri]
+ if not obj then
+ return nil
+ end
+ return obj.text
+end
+
+function mt:removeText(uri)
+ self._file[uri] = nil
+end
+
+function mt:start(method)
+ self._method = method
+ while true do
+ local header = self:read 'l'
+ if not header then
+ return
+ end
+ if header:sub(1, #'Content-Length') == 'Content-Length' then
+ self:_readAsContent(header)
+ elseif header:sub(1, #'Content-Type') == 'Content-Type' then
+ else
+ log.error('错误的协议头:', header)
+ end
+ end
+ return true
+end
+
+function mt:stop()
+ self._input = nil
+ self._output = nil
+end
+
+return function ()
+ return setmetatable({
+ _file = {},
+ }, mt)
+end
diff --git a/server/src/matcher/definition.lua b/server/src/matcher/definition.lua
new file mode 100644
index 00000000..a744efed
--- /dev/null
+++ b/server/src/matcher/definition.lua
@@ -0,0 +1,282 @@
+local parser = require 'parser'
+
+local pos
+local defs = {}
+local scopes
+local result
+local namePos
+local colonPos
+
+local DUMMY_TABLE = {}
+
+local function scopeInit()
+ scopes = {{}}
+end
+
+local function scopeDef(obj)
+ local scope = scopes[#scopes]
+ local name = obj[1]
+ scope[name] = obj
+end
+
+local function scopeGet(name)
+ for i = #scopes, 1, -1 do
+ local scope = scopes[i]
+ local obj = scope[name]
+ if obj then
+ return obj
+ end
+ end
+ return nil
+end
+
+local function scopeSet(obj)
+ local name = obj[1]
+ local scope = scopes[#scopes]
+ if not scope[name] then
+ scope[name] = obj
+ end
+end
+
+local function globalSet(obj)
+ local name = obj[1]
+ if not scopeGet(name) then
+ local scope = scopes[1]
+ scope[name] = obj
+ end
+end
+
+local function scopePush()
+ scopes[#scopes+1] = {}
+end
+
+local function scopePop()
+ scopes[#scopes] = nil
+end
+
+local function checkDifinition(name, p)
+ if pos < p or pos > p + #name then
+ return
+ end
+ result = scopeGet(name)
+end
+
+function defs.NamePos(p)
+ namePos = p
+end
+
+function defs.Name(str)
+ checkDifinition(str, namePos)
+ return {str, namePos, type = 'name'}
+end
+
+function defs.DOTSPos(p)
+ namePos = p
+end
+
+function defs.DOTS(str)
+ checkDifinition(str, namePos)
+ return {str, namePos, type = 'name'}
+end
+
+function defs.COLONPos(p)
+ colonPos = p
+end
+
+function defs.ColonName(name)
+ name.colon = colonPos
+ return name
+end
+
+function defs.LocalVar(names)
+ for _, name in ipairs(names) do
+ scopeDef(name)
+ end
+end
+
+function defs.LocalSet(names)
+ for _, name in ipairs(names) do
+ scopeDef(name)
+ end
+end
+
+function defs.Set(simples)
+ for _, simple in ipairs(simples) do
+ if simple.type == 'simple' and #simple == 1 then
+ local obj = simple[1]
+ local name = obj[1]
+ globalSet(obj)
+ end
+ end
+end
+
+function defs.Simple(...)
+ return { type = 'simple', ... }
+end
+
+function defs.ArgList(...)
+ if ... == '' then
+ return DUMMY_TABLE
+ end
+ return { type = 'list', ... }
+end
+
+function defs.FuncName(...)
+ if ... == '' then
+ return DUMMY_TABLE
+ end
+ return { type = 'simple', ... }
+end
+
+function defs.FunctionDef(simple, args)
+ if #simple == 1 then
+ globalSet(simple[1])
+ end
+ scopePush()
+ -- 判断隐藏的局部变量self
+ if #simple > 0 then
+ local name = simple[#simple]
+ if name.colon then
+ scopeDef {'self', name.colon, name.colon, type = 'name'}
+ end
+ end
+ for _, arg in ipairs(args) do
+ if arg.type == 'simple' and #arg == 1 then
+ local name = arg[1]
+ scopeDef(name)
+ end
+ if arg.type == 'name' then
+ scopeDef(arg)
+ end
+ end
+end
+
+function defs.FunctionLoc(simple, args)
+ if #simple == 1 then
+ scopeDef(simple[1])
+ end
+ scopePush()
+ -- 判断隐藏的局部变量self
+ if #simple > 0 then
+ local name = simple[#simple]
+ if name.colon then
+ scopeDef {'self', name.colon, name.colon, type = 'name'}
+ end
+ end
+ for _, arg in ipairs(args) do
+ if arg.type == 'simple' and #arg == 1 then
+ local name = arg[1]
+ scopeDef(name)
+ end
+ if arg.type == 'name' then
+ scopeDef(arg)
+ end
+ end
+end
+
+function defs.Function()
+ scopePop()
+end
+
+function defs.DoDef()
+ scopePush()
+end
+
+function defs.Do()
+ scopePop()
+end
+
+function defs.IfDef()
+ scopePush()
+end
+
+function defs.If()
+ scopePop()
+end
+
+function defs.ElseIfDef()
+ scopePush()
+end
+
+function defs.ElseIf()
+ scopePop()
+end
+
+function defs.ElseDef()
+ scopePush()
+end
+
+function defs.Else()
+ scopePop()
+end
+
+function defs.LoopDef(name)
+ scopePush()
+ scopeDef(name)
+end
+
+function defs.Loop()
+ scopePop()
+end
+
+function defs.LoopStart(name, exp)
+ return name
+end
+
+function defs.NameList(...)
+ return { type = 'list', ... }
+end
+
+function defs.SimpleList(...)
+ return { type = 'list', ... }
+end
+
+function defs.InDef(names)
+ scopePush()
+ for _, name in ipairs(names) do
+ scopeDef(name)
+ end
+end
+
+function defs.In()
+ scopePop()
+end
+
+function defs.WhileDef()
+ scopePush()
+end
+
+function defs.While()
+ scopePop()
+end
+
+function defs.RepeatDef()
+ scopePush()
+end
+
+function defs.Until()
+ scopePop()
+end
+
+return function (buf, pos_)
+ pos = pos_
+ result = nil
+ scopeInit()
+
+ local suc, err = parser.grammar(buf, 'Lua', defs)
+ if not suc then
+ return false, '语法错误', err
+ end
+
+ if not result then
+ return false, 'No word'
+ end
+ local name, start, finish = result[1], result[2], result[3]
+ if not start then
+ return false, 'No match'
+ end
+ if not finish then
+ finish = start + #name - 1
+ end
+ return true, start, finish
+end
diff --git a/server/src/matcher/implementation.lua b/server/src/matcher/implementation.lua
new file mode 100644
index 00000000..bd28ee3a
--- /dev/null
+++ b/server/src/matcher/implementation.lua
@@ -0,0 +1,377 @@
+local parser = require 'parser'
+
+local pos
+local defs = {}
+local scopes
+local logics
+local result
+local namePos
+local colonPos
+
+local DUMMY_TABLE = {}
+
+local function logicPush()
+ logics[#logics+1] = 0
+end
+
+local function logicPop()
+ logics[#logics] = nil
+end
+
+local function logicAdd()
+ logics[#logics] = logics[#logics] + 1
+end
+
+local function logicGet()
+ local list = {}
+ for i = 1, #logics do
+ list[i] = logics[i]
+ end
+ return list
+end
+
+local function scopeInit()
+ scopes = {{}}
+end
+
+local function scopeGet(name)
+ for i = #scopes, 1, -1 do
+ local scope = scopes[i]
+ local list = scope[name]
+ if list then
+ return list
+ end
+ end
+ return nil
+end
+
+local function scopeSet(obj)
+ obj.logic = logicGet()
+ local name = obj[1]
+ local scope = scopes[#scopes]
+ local list = scope[name]
+ if list then
+ list[#list+1] = obj
+ else
+ scope[name] = {obj}
+ end
+end
+
+local function scopePush()
+ scopes[#scopes+1] = {}
+end
+
+local function scopePop()
+ scopes[#scopes] = nil
+end
+
+local function globalSet(obj)
+ obj.logic = logicGet()
+ local name = obj[1]
+ for i = #scopes, 1, -1 do
+ local scope = scopes[i]
+ local list = scope[name]
+ if list then
+ list[#list+1] = obj
+ return
+ end
+ end
+ local scope = scopes[1]
+ scope[name] = {obj}
+end
+
+local function sameLogic(cur, target)
+ for i = 1, #cur do
+ if target[i] == nil then
+ break
+ end
+ if cur[i] ~= target[i] then
+ return false
+ end
+ end
+ return true
+end
+
+local function mustCovered(results, target)
+ for _, result in ipairs(results) do
+ local logic = result.logic
+ if #logic == #target then
+ local isSame = true
+ for i = 1, #logic do
+ if logic[i] ~= target[i] then
+ isSame = false
+ end
+ end
+ if isSame then
+ return true
+ end
+ end
+ end
+ return false
+end
+
+local function checkImplementation(name, p)
+ if result ~= nil then
+ return
+ end
+ if pos < p or pos > p + #name then
+ return
+ end
+ local list = scopeGet(name)
+ if list then
+ local logic = logicGet()
+ result = {}
+ for i = #list, 1, -1 do
+ local obj = list[i]
+ local name, start, finish = obj[1], obj[2], obj[3]
+ if not finish then
+ finish = start + #name - 1
+ end
+ -- 如果不在同一个分支里,则跳过
+ if not sameLogic(logic, obj.logic) then
+ goto CONTINUE
+ end
+ -- 如果该分支已经有确定值,则跳过
+ if mustCovered(result, obj.logic) then
+ goto CONTINUE
+ end
+ result[#result+1] = {start, finish, logic = obj.logic}
+ -- 如果分支长度比自己小,则一定是确信值,不用再继续找了
+ if #obj.logic <= #logic then
+ break
+ end
+ ::CONTINUE::
+ end
+ else
+ result = false
+ end
+end
+
+function defs.NamePos(p)
+ namePos = p
+end
+
+function defs.Name(str)
+ checkImplementation(str, namePos)
+ return {str, namePos, type = 'name'}
+end
+
+function defs.DOTSPos(p)
+ namePos = p
+end
+
+function defs.DOTS(str)
+ checkImplementation(str, namePos)
+ return {str, namePos, type = 'name'}
+end
+
+function defs.COLONPos(p)
+ colonPos = p
+end
+
+function defs.ColonName(name)
+ name.colon = colonPos
+ return name
+end
+
+function defs.LocalVar(names)
+ for _, name in ipairs(names) do
+ scopeSet(name)
+ end
+end
+
+function defs.LocalSet(names)
+ for _, name in ipairs(names) do
+ scopeSet(name)
+ end
+end
+
+function defs.Set(simples)
+ for _, simple in ipairs(simples) do
+ if simple.type == 'simple' and #simple == 1 then
+ local obj = simple[1]
+ local name = obj[1]
+ globalSet(obj)
+ end
+ end
+end
+
+function defs.Simple(...)
+ return { type = 'simple', ... }
+end
+
+function defs.ArgList(...)
+ if ... == '' then
+ return DUMMY_TABLE
+ end
+ return { type = 'list', ... }
+end
+
+function defs.FuncName(...)
+ if ... == '' then
+ return DUMMY_TABLE
+ end
+ return { type = 'simple', ... }
+end
+
+function defs.FunctionDef(simple, args)
+ if #simple == 1 then
+ globalSet(simple[1])
+ end
+ scopePush()
+ -- 判断隐藏的局部变量self
+ if #simple > 0 then
+ local name = simple[#simple]
+ if name.colon then
+ scopeSet {'self', name.colon, name.colon, type = 'name'}
+ end
+ end
+ for _, arg in ipairs(args) do
+ if arg.type == 'simple' and #arg == 1 then
+ local name = arg[1]
+ scopeSet(name)
+ end
+ if arg.type == 'name' then
+ scopeSet(arg)
+ end
+ end
+end
+
+function defs.FunctionLoc(simple, args)
+ if #simple == 1 then
+ scopeSet(simple[1])
+ end
+ scopePush()
+ -- 判断隐藏的局部变量self
+ if #simple > 0 then
+ local name = simple[#simple]
+ if name.colon then
+ scopeSet {'self', name.colon, name.colon, type = 'name'}
+ end
+ end
+ for _, arg in ipairs(args) do
+ if arg.type == 'simple' and #arg == 1 then
+ local name = arg[1]
+ scopeSet(name)
+ end
+ if arg.type == 'name' then
+ scopeSet(arg)
+ end
+ end
+end
+
+function defs.Function()
+ scopePop()
+end
+
+function defs.DoDef()
+ scopePush()
+end
+
+function defs.Do()
+ scopePop()
+end
+
+function defs.IfDef()
+ logicPush()
+ scopePush()
+end
+
+function defs.If()
+ scopePop()
+end
+
+function defs.ElseIfDef()
+ logicAdd()
+ scopePush()
+end
+
+function defs.ElseIf()
+ scopePop()
+end
+
+function defs.ElseDef()
+ logicAdd()
+ scopePush()
+end
+
+function defs.Else()
+ scopePop()
+end
+
+function defs.EndIf()
+ logicPop()
+end
+
+function defs.LoopDef(name)
+ logicPush()
+ scopePush()
+ scopeSet(name)
+end
+
+function defs.Loop()
+ scopePop()
+ logicPop()
+end
+
+function defs.LoopStart(name, exp)
+ return name
+end
+
+function defs.NameList(...)
+ return { type = 'list', ... }
+end
+
+function defs.SimpleList(...)
+ return { type = 'list', ... }
+end
+
+function defs.InDef(names)
+ logicPush()
+ scopePush()
+ for _, name in ipairs(names) do
+ scopeSet(name)
+ end
+end
+
+function defs.In()
+ scopePop()
+ logicPop()
+end
+
+function defs.WhileDef()
+ logicPush()
+ scopePush()
+end
+
+function defs.While()
+ scopePop()
+ logicPop()
+end
+
+function defs.RepeatDef()
+ logicPush()
+ scopePush()
+end
+
+function defs.Until()
+ scopePop()
+ logicPop()
+end
+
+return function (buf, pos_)
+ pos = pos_
+ result = nil
+ logics = {}
+ scopeInit()
+
+ local suc, err = parser.grammar(buf, 'Lua', defs)
+ if not suc then
+ return false, '语法错误', err
+ end
+
+ if not result then
+ return false, 'No word'
+ end
+ return true, result
+end
diff --git a/server/src/matcher/init.lua b/server/src/matcher/init.lua
new file mode 100644
index 00000000..c570b342
--- /dev/null
+++ b/server/src/matcher/init.lua
@@ -0,0 +1,6 @@
+local api = {
+ definition = require 'matcher.definition',
+ implementation = require 'matcher.implementation',
+}
+
+return api
diff --git a/server/src/method/exit.lua b/server/src/method/exit.lua
new file mode 100644
index 00000000..716a86e7
--- /dev/null
+++ b/server/src/method/exit.lua
@@ -0,0 +1,3 @@
+return function ()
+ return true
+end
diff --git a/server/src/method/init.lua b/server/src/method/init.lua
new file mode 100644
index 00000000..a99c3ed5
--- /dev/null
+++ b/server/src/method/init.lua
@@ -0,0 +1,17 @@
+local method = {}
+
+local function init(name)
+ method[name] = require('method.' .. name:gsub('/', '.'))
+end
+
+init 'exit'
+init 'initialize'
+init 'initialized'
+init 'shutdown'
+init 'textDocument/implementation'
+init 'textDocument/definition'
+init 'textDocument/didOpen'
+init 'textDocument/didChange'
+init 'textDocument/didClose'
+
+return method
diff --git a/server/src/method/initialize.lua b/server/src/method/initialize.lua
new file mode 100644
index 00000000..866ded66
--- /dev/null
+++ b/server/src/method/initialize.lua
@@ -0,0 +1,18 @@
+return function (lsp, data)
+ lsp._inited = true
+ return {
+ capabilities = {
+ -- 支持“转到定义”
+ definitionProvider = true,
+ -- 支持“转到实现”
+ implementationProvider = true,
+ -- 文本同步方式
+ textDocumentSync = {
+ -- 打开关闭文本时通知
+ openClose = true,
+ -- 文本改变时完全通知 TODO 支持差量更新(2)
+ change = 1,
+ }
+ }
+ }
+end
diff --git a/server/src/method/initialized.lua b/server/src/method/initialized.lua
new file mode 100644
index 00000000..0451dc50
--- /dev/null
+++ b/server/src/method/initialized.lua
@@ -0,0 +1,3 @@
+return function (lsp)
+ return true
+end
diff --git a/server/src/method/shutdown.lua b/server/src/method/shutdown.lua
new file mode 100644
index 00000000..0451dc50
--- /dev/null
+++ b/server/src/method/shutdown.lua
@@ -0,0 +1,3 @@
+return function (lsp)
+ return true
+end
diff --git a/server/src/method/textDocument/definition.lua b/server/src/method/textDocument/definition.lua
new file mode 100644
index 00000000..3a17b463
--- /dev/null
+++ b/server/src/method/textDocument/definition.lua
@@ -0,0 +1,46 @@
+local parser = require 'parser'
+local matcher = require 'matcher'
+
+return function (lsp, params)
+ local uri = params.textDocument.uri
+ local text = lsp:loadText(uri)
+ if not text then
+ return nil, '找不到文件:' .. uri
+ end
+ local start_clock = os.clock()
+ -- lua是从1开始的,因此都要+1
+ local pos = parser.calcline.position_utf8(text, params.position.line + 1, params.position.character + 1)
+ local suc, start, finish = matcher.definition(text, pos)
+ if not suc then
+ if finish then
+ log.debug(start, uri)
+ finish.lua = nil
+ log.debug(table.dump(finish))
+ end
+ return {}
+ end
+
+ local start_row, start_col = parser.calcline.rowcol_utf8(text, start)
+ local finish_row, finish_col = parser.calcline.rowcol_utf8(text, finish)
+
+ local response = {
+ uri = uri,
+ range = {
+ start = {
+ line = start_row - 1,
+ character = start_col - 1,
+ },
+ ['end'] = {
+ line = finish_row - 1,
+ -- 这里不用-1,因为前端期待的是匹配完成后的位置
+ character = finish_col,
+ },
+ },
+ }
+ local passed_clock = os.clock() - start_clock
+ if passed_clock >= 0.01 then
+ log.warn(('[转到定义]耗时[%.3f]秒,文件大小[%s]字节'):format(passed_clock, #text))
+ end
+
+ return response
+end
diff --git a/server/src/method/textDocument/didChange.lua b/server/src/method/textDocument/didChange.lua
new file mode 100644
index 00000000..6856b729
--- /dev/null
+++ b/server/src/method/textDocument/didChange.lua
@@ -0,0 +1,7 @@
+return function (lsp, params)
+ local doc = params.textDocument
+ local change = params.contentChanges
+ -- TODO 支持差量更新
+ lsp:saveText(doc.uri, doc.version, change[1].text)
+ return true
+end
diff --git a/server/src/method/textDocument/didClose.lua b/server/src/method/textDocument/didClose.lua
new file mode 100644
index 00000000..d4edb624
--- /dev/null
+++ b/server/src/method/textDocument/didClose.lua
@@ -0,0 +1,5 @@
+return function (lsp, params)
+ local doc = params.textDocument
+ lsp:removeText(doc.uri, doc.version)
+ return true
+end
diff --git a/server/src/method/textDocument/didOpen.lua b/server/src/method/textDocument/didOpen.lua
new file mode 100644
index 00000000..27fdda71
--- /dev/null
+++ b/server/src/method/textDocument/didOpen.lua
@@ -0,0 +1,5 @@
+return function (lsp, params)
+ local doc = params.textDocument
+ lsp:saveText(doc.uri, doc.version, doc.text)
+ return true
+end
diff --git a/server/src/method/textDocument/implementation.lua b/server/src/method/textDocument/implementation.lua
new file mode 100644
index 00000000..6d3cd0ac
--- /dev/null
+++ b/server/src/method/textDocument/implementation.lua
@@ -0,0 +1,51 @@
+local parser = require 'parser'
+local matcher = require 'matcher'
+
+return function (lsp, params)
+ local uri = params.textDocument.uri
+ local text = lsp:loadText(uri)
+ if not text then
+ return nil, '找不到文件:' .. uri
+ end
+ local start_clock = os.clock()
+ -- lua是从1开始的,因此都要+1
+ local pos = parser.calcline.position_utf8(text, params.position.line + 1, params.position.character + 1)
+ local suc, results, info = matcher.implementation(text, pos)
+ if not suc then
+ if info then
+ log.debug(results, uri)
+ info.lua = nil
+ log.debug(table.dump(info))
+ end
+ return {}
+ end
+
+ local locations = {}
+ for i, result in ipairs(results) do
+ local start, finish = result[1], result[2]
+ local start_row, start_col = parser.calcline.rowcol_utf8(text, start)
+ local finish_row, finish_col = parser.calcline.rowcol_utf8(text, finish)
+ locations[i] = {
+ uri = uri,
+ range = {
+ start = {
+ line = start_row - 1,
+ character = start_col - 1,
+ },
+ ['end'] = {
+ line = finish_row - 1,
+ -- 这里不用-1,因为前端期待的是匹配完成后的位置
+ character = finish_col,
+ },
+ }
+ }
+ end
+
+ local response = locations
+ local passed_clock = os.clock() - start_clock
+ if passed_clock >= 0.01 then
+ log.warn(('[转到实现]耗时[%.3f]秒,文件大小[%s]字节'):format(passed_clock, #text))
+ end
+
+ return response
+end
diff --git a/server/src/parser/calcline.lua b/server/src/parser/calcline.lua
new file mode 100644
index 00000000..26f475d9
--- /dev/null
+++ b/server/src/parser/calcline.lua
@@ -0,0 +1,93 @@
+local m = require 'lpeglabel'
+
+local row
+local fl
+local NL = (m.P'\r\n' + m.S'\r\n') * m.Cp() / function (pos)
+ row = row + 1
+ fl = pos
+end
+local ROWCOL = (NL + m.P(1))^0
+local function rowcol(str, n)
+ row = 1
+ fl = 1
+ ROWCOL:match(str:sub(1, n))
+ local col = n - fl + 1
+ return row, col
+end
+
+local function rowcol_utf8(str, n)
+ row = 1
+ fl = 1
+ ROWCOL:match(str:sub(1, n))
+ return row, utf8.len(str, fl, n)
+end
+
+local function position(str, _row, _col)
+ local cur = 1
+ local row = 1
+ while true do
+ if row == _row then
+ return cur + _col - 1
+ elseif row > _row then
+ return cur - 1
+ end
+ local pos = str:find('[\r\n]', cur)
+ if not pos then
+ return #str
+ end
+ row = row + 1
+ if str:sub(pos, pos+1) == '\r\n' then
+ cur = pos + 2
+ else
+ cur = pos + 1
+ end
+ end
+end
+
+local function position_utf8(str, _row, _col)
+ local cur = 1
+ local row = 1
+ while true do
+ if row == _row then
+ return utf8.offset(str, _col, cur)
+ elseif row > _row then
+ return cur - 1
+ end
+ local pos = str:find('[\r\n]', cur)
+ if not pos then
+ return #str
+ end
+ row = row + 1
+ if str:sub(pos, pos+1) == '\r\n' then
+ cur = pos + 2
+ else
+ cur = pos + 1
+ end
+ end
+end
+
+local NL = m.P'\r\n' + m.S'\r\n'
+
+local function line(str, row)
+ local count = 0
+ local res
+ local LINE = m.Cmt((1 - NL)^0, function (_, _, c)
+ count = count + 1
+ if count == row then
+ res = c
+ return false
+ end
+ return true
+ end)
+ local MATCH = (LINE * NL)^0 * LINE
+ MATCH:match(str)
+ return res
+end
+
+return {
+ rowcol = rowcol,
+ rowcol_utf8 = rowcol_utf8,
+ position = position,
+ position_utf8 = position_utf8,
+ line = line,
+}
diff --git a/server/src/parser/grammar.lua b/server/src/parser/grammar.lua
new file mode 100644
index 00000000..1a4f105d
--- /dev/null
+++ b/server/src/parser/grammar.lua
@@ -0,0 +1,336 @@
+local re = require 'parser.relabel'
+local m = require 'lpeglabel'
+local calcline = require 'parser.calcline'
+
+local scriptBuf = ''
+local compiled = {}
+local parser
+
+local defs = setmetatable({}, {__index = function (self, key)
+ self[key] = function (...)
+ if parser[key] then
+ return parser[key](...)
+ end
+ end
+ return self[key]
+end})
+
+defs.nl = (m.P'\r\n' + m.S'\r\n') / function ()
+ if parser.nl then
+ return parser.nl()
+ end
+end
+defs.s = m.S' \t'
+defs.S = - defs.s
+defs.ea = '\a'
+defs.eb = '\b'
+defs.ef = '\f'
+defs.en = '\n'
+defs.er = '\r'
+defs.et = '\t'
+defs.ev = '\v'
+local eof = re.compile '!. / %{SYNTAX_ERROR}'
+
+local function grammar(tag)
+ return function (script)
+ scriptBuf = script .. '\r\n' .. scriptBuf
+ compiled[tag] = re.compile(scriptBuf, defs) * eof
+ end
+end
+
+local labels = {
+
+}
+
+local function errorpos(lua, pos, err)
+ local row, col = calcline.rowcol(lua, pos)
+ local str = calcline.line(lua, row)
+ return {
+ lua = lua,
+ line = row,
+ pos = col,
+ err = err,
+ code = str,
+ level = 'error',
+ }
+end
+
+grammar 'Comment' [[
+Comment <- '--' (LongComment / ShortComment)
+LongComment <- '[' {:eq: '='* :} '[' CommentClose
+CommentClose <- ']' =eq ']' / . CommentClose
+ShortComment <- (!%nl .)*
+]]
+
+grammar 'Sp' [[
+Sp <- (Comment / %nl / %s)*
+]]
+
+grammar 'Common' [[
+Cut <- ![a-zA-Z0-9_]
+X16 <- [a-fA-F0-9]
+
+AND <- Sp 'and' Cut
+BREAK <- Sp 'break' Cut
+DO <- Sp 'do' Cut
+ELSE <- Sp 'else' Cut
+ELSEIF <- Sp 'elseif' Cut
+END <- Sp 'end' Cut
+FALSE <- Sp 'false' Cut
+FOR <- Sp 'for' Cut
+FUNCTION <- Sp 'function' Cut
+GOTO <- Sp 'goto' Cut
+IF <- Sp 'if' Cut
+IN <- Sp 'in' Cut
+LOCAL <- Sp 'local' Cut
+NIL <- Sp 'nil' Cut
+NOT <- Sp 'not' Cut
+OR <- Sp 'or' Cut
+REPEAT <- Sp 'repeat' Cut
+RETURN <- Sp 'return' Cut
+THEN <- Sp 'then' Cut
+TRUE <- Sp 'true' Cut
+UNTIL <- Sp 'until' Cut
+WHILE <- Sp 'while' Cut
+
+Esc <- '\' EChar
+EChar <- 'a' -> ea
+ / 'b' -> eb
+ / 'f' -> ef
+ / 'n' -> en
+ / 'r' -> er
+ / 't' -> et
+ / 'v' -> ev
+ / '\'
+ / '"'
+ / "'"
+ / %nl
+ / 'z' (%nl / %s)* -> ''
+ / 'x' X16 X16
+ / [0-9] [0-9]? [0-9]?
+ / 'u{' X16^+1^-6 '}'
+
+Comp <- Sp CompList
+CompList <- '<='
+ / '>='
+ / '<'
+ / '>'
+ / '~='
+ / '=='
+BOR <- Sp '|'
+BXOR <- Sp '~'
+BAND <- Sp '&'
+Bshift <- Sp BshiftList
+BshiftList <- '<<'
+ / '>>'
+Concat <- Sp '..'
+Adds <- Sp AddsList
+AddsList <- '+'
+ / '-'
+Muls <- Sp MulsList
+MulsList <- '*'
+ / '//'
+ / '/'
+ / '%'
+Unary <- Sp UnaryList
+UnaryList <- 'not'
+ / '#'
+ / '-'
+ / '~'
+POWER <- Sp '^'
+
+PL <- Sp '('
+PR <- Sp ')'
+BL <- Sp '['
+BR <- Sp ']'
+TL <- Sp '{'
+TR <- Sp '}'
+COMMA <- Sp ','
+SEMICOLON <- Sp ';'
+DOTS <- Sp {} -> DOTSPos
+ '...' -> DOTS
+DOT <- Sp '.'
+COLON <- Sp {} -> COLONPos
+ ':' -> COLON
+LABEL <- Sp '::'
+ASSIGN <- Sp '='
+]]
+
+grammar 'Nil' [[
+Nil <- NIL
+]]
+
+grammar 'Boolean' [[
+Boolean <- TRUE
+ / FALSE
+]]
+
+grammar 'String' [[
+String <- Sp StringDef
+StringDef <- '"' (Esc / !%nl !'"' .)* '"'
+ / "'" (Esc / !%nl !"'" .)* "'"
+ / '[' {:eq: '='* :} '[' StringClose
+StringClose <- ']' =eq ']' / . StringClose
+]]
+
+grammar 'Number' [[
+Number <- Sp NumberDef
+NumberDef <- Number16 / Number10
+
+Number10 <- Integer10 Float10
+Integer10 <- '0' / [1-9] [0-9]*
+Float10 <- ('.' [0-9]*)? ([eE] [+-]? [1-9]? [0-9]*)?
+
+Number16 <- Integer16 Float16
+Integer16 <- '0' [xX] X16*
+Float16 <- ('.' X16*)? ([pP] [+-]? [1-9]? [0-9]*)?
+]]
+
+grammar 'Name' [[
+Name <- Sp {} -> NamePos
+ {[a-zA-Z_] [a-zA-Z0-9_]*} -> Name
+]]
+
+grammar 'Exp' [[
+Exp <- ExpOr
+ExpOr <- ExpAnd (OR ExpAnd)*
+ExpAnd <- ExpCompare (AND ExpCompare)*
+ExpCompare <- ExpBor (Comp ExpBor)*
+ExpBor <- ExpBxor (BOR ExpBxor)*
+ExpBxor <- ExpBand (BXOR ExpBand)*
+ExpBand <- ExpBshift (BAND ExpBshift)*
+ExpBshift <- ExpConcat (Bshift ExpConcat)*
+ExpConcat <- ExpAdds (Concat ExpAdds)*
+ExpAdds <- ExpMuls (Adds ExpMuls)*
+ExpMuls <- ExpUnary (Muls ExpUnary)*
+ExpUnary <- (Unary ExpPower)
+ / ExpPower
+ExpPower <- ExpUnit (POWER ExpUnary)*
+ExpUnit <- Nil
+ / Boolean
+ / String
+ / Number
+ / DOTS
+ / Table
+ / Function
+ / Simple
+
+Simple <- (Prefix (Suffix)*)
+ -> Simple
+Prefix <- PL Exp PR
+ / Name
+ColonName <- (COLON Name)
+ -> ColonName
+Suffix <- DOT Name
+ / ColonName
+ / Table
+ / String
+ / BL Exp BR
+ / PL ArgList? PR
+
+ArgList <- (Arg (COMMA Arg)*)?
+ -> ArgList
+Arg <- DOTS
+ / Exp
+
+Table <- TL TableFields? TR
+TableFields <- TableField (TableSep TableField)* TableSep?
+TableSep <- COMMA / SEMICOLON
+TableField <- NewIndex / NewField / Exp
+NewIndex <- BL Exp BR ASSIGN Exp
+NewField <- Name ASSIGN Exp
+
+Function <- FunctionLoc / FunctionDef
+FunctionLoc <- (LOCAL FUNCTION FuncName PL ArgList PR) -> FunctionLoc
+ (!END Action)* -> Function
+ END
+FunctionDef <- (FUNCTION FuncName PL ArgList PR) -> FunctionDef
+ (!END Action)* -> Function
+ END
+FuncName <- (Name (FuncSuffix)*)?
+ -> FuncName
+FuncSuffix <- DOT Name
+ / ColonName
+
+-- 纯占位,修改了 `relabel.lua` 使重复定义不抛错
+Action <- !. .
+]]
+
+grammar 'Action' [[
+Action <- SEMICOLON / Do / Break / Return / Label / GoTo / If / For / While / Repeat / Function / Set / Local / Call
+
+ExpList <- Exp (COMMA Exp)*
+NameList <- (Name (COMMA Name)*) -> NameList
+SimpleList <- (Simple (COMMA Simple)*) -> SimpleList
+
+Do <- DO -> DoDef
+ (!END Action)* -> Do
+ END
+
+Break <- BREAK
+
+Return <- RETURN !END ExpList?
+
+Label <- LABEL Name LABEL
+
+GoTo <- GOTO Name
+
+If <- IfPart
+ ElseIfPart*
+ ElsePart?
+ END
+ -> EndIf
+IfPart <- (IF Exp THEN) -> IfDef
+ (!ELSEIF !ELSE !END Action)* -> If
+ElseIfPart <- (ELSEIF Exp THEN) -> ElseIfDef
+ (!ELSE !ELSEIF !END Action)* -> ElseIf
+ElsePart <- ELSE -> ElseDef
+ (!END Action)* -> Else
+
+For <- Loop / In
+
+Loop <- (FOR LoopStart LoopFinish LoopStep? DO) -> LoopDef
+ (!END Action)* -> Loop
+ END
+LoopStart <- (Name ASSIGN Exp) -> LoopStart
+LoopFinish <- COMMA Exp
+LoopStep <- COMMA Exp
+
+In <- (FOR NameList IN ExpList DO) -> InDef
+ (!END Action)* -> In
+ END
+
+While <- (WHILE Exp DO) -> WhileDef
+ (!END Action)* -> While
+ END
+
+Repeat <- REPEAT -> RepeatDef
+ (!UNTIL Action)* -> Repeat
+ (UNTIL Exp) -> Until
+
+Set <- (LOCAL NameList ASSIGN ExpList)
+ -> LocalSet
+ / (SimpleList ASSIGN ExpList)
+ -> Set
+
+Local <- LOCAL NameList
+ -> LocalVar
+
+Call <- Prefix (Suffix)*
+]]
+
+grammar 'Lua' [[
+Lua <- (Sp Action)* Sp
+]]
+
+return function (lua, mode, parser_)
+ parser = parser_ or {}
+ mode = mode or 'lua'
+ local r, e, pos = compiled[mode]:match(lua)
+ if not r then
+ local err = errorpos(lua, pos, e)
+ return nil, err
+ end
+
+ return r
+end
diff --git a/server/src/parser/init.lua b/server/src/parser/init.lua
new file mode 100644
index 00000000..3216fa39
--- /dev/null
+++ b/server/src/parser/init.lua
@@ -0,0 +1,7 @@
+local api = {
+ grammar = require 'parser.grammar',
+ split = require 'parser.split',
+ calcline = require 'parser.calcline',
+}
+
+return api
diff --git a/server/src/parser/relabel.lua b/server/src/parser/relabel.lua
new file mode 100644
index 00000000..bc832017
--- /dev/null
+++ b/server/src/parser/relabel.lua
@@ -0,0 +1,364 @@
+-- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $
+
+-- imported functions and modules
+local tonumber, type, print, error = tonumber, type, print, error
+local pcall = pcall
+local setmetatable = setmetatable
+local tinsert, concat = table.insert, table.concat
+local rep = string.rep
+local m = require"lpeglabel"
+
+-- 'm' will be used to parse expressions, and 'mm' will be used to
+-- create expressions; that is, 're' runs on 'm', creating patterns
+-- on 'mm'
+local mm = m
+
+-- pattern's metatable
+local mt = getmetatable(mm.P(0))
+
+
+
+-- No more global accesses after this point
+local version = _VERSION
+if version == "Lua 5.2" then _ENV = nil end
+
+
+local any = m.P(1)
+local dummy = mm.P(false)
+
+
+local errinfo = {
+ NoPatt = "no pattern found",
+ ExtraChars = "unexpected characters after the pattern",
+
+ ExpPatt1 = "expected a pattern after '/'",
+
+ ExpPatt2 = "expected a pattern after '&'",
+ ExpPatt3 = "expected a pattern after '!'",
+
+ ExpPatt4 = "expected a pattern after '('",
+ ExpPatt5 = "expected a pattern after ':'",
+ ExpPatt6 = "expected a pattern after '{~'",
+ ExpPatt7 = "expected a pattern after '{|'",
+
+ ExpPatt8 = "expected a pattern after '<-'",
+
+ ExpPattOrClose = "expected a pattern or closing '}' after '{'",
+
+ ExpNumName = "expected a number, '+', '-' or a name (no space) after '^'",
+ ExpCap = "expected a string, number, '{}' or name after '->'",
+
+ ExpName1 = "expected the name of a rule after '=>'",
+ ExpName2 = "expected the name of a rule after '=' (no space)",
+ ExpName3 = "expected the name of a rule after '<' (no space)",
+
+ ExpLab1 = "expected a label after '{'",
+
+ ExpNameOrLab = "expected a name or label after '%' (no space)",
+
+ ExpItem = "expected at least one item after '[' or '^'",
+
+ MisClose1 = "missing closing ')'",
+ MisClose2 = "missing closing ':}'",
+ MisClose3 = "missing closing '~}'",
+ MisClose4 = "missing closing '|}'",
+ MisClose5 = "missing closing '}'", -- for the captures
+
+ MisClose6 = "missing closing '>'",
+ MisClose7 = "missing closing '}'", -- for the labels
+
+ MisClose8 = "missing closing ']'",
+
+ MisTerm1 = "missing terminating single quote",
+ MisTerm2 = "missing terminating double quote",
+}
+
+local function expect (pattern, label)
+ return pattern + m.T(label)
+end
+
+
+-- Pre-defined names
+local Predef = { nl = m.P"\n" }
+
+
+local mem
+local fmem
+local gmem
+
+
+local function updatelocale ()
+ mm.locale(Predef)
+ Predef.a = Predef.alpha
+ Predef.c = Predef.cntrl
+ Predef.d = Predef.digit
+ Predef.g = Predef.graph
+ Predef.l = Predef.lower
+ Predef.p = Predef.punct
+ Predef.s = Predef.space
+ Predef.u = Predef.upper
+ Predef.w = Predef.alnum
+ Predef.x = Predef.xdigit
+ Predef.A = any - Predef.a
+ Predef.C = any - Predef.c
+ Predef.D = any - Predef.d
+ Predef.G = any - Predef.g
+ Predef.L = any - Predef.l
+ Predef.P = any - Predef.p
+ Predef.S = any - Predef.s
+ Predef.U = any - Predef.u
+ Predef.W = any - Predef.w
+ Predef.X = any - Predef.x
+ mem = {} -- restart memoization
+ fmem = {}
+ gmem = {}
+ local mt = {__mode = "v"}
+ setmetatable(mem, mt)
+ setmetatable(fmem, mt)
+ setmetatable(gmem, mt)
+end
+
+
+updatelocale()
+
+
+
+local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end)
+
+
+local function getdef (id, defs)
+ local c = defs and defs[id]
+ if not c then
+ error("undefined name: " .. id)
+ end
+ return c
+end
+
+
+local function mult (p, n)
+ local np = mm.P(true)
+ while n >= 1 do
+ if n%2 >= 1 then np = np * p end
+ p = p * p
+ n = n/2
+ end
+ return np
+end
+
+local function equalcap (s, i, c)
+ if type(c) ~= "string" then return nil end
+ local e = #c + i
+ if s:sub(i, e - 1) == c then return e else return nil end
+end
+
+
+local S = (Predef.space + "--" * (any - Predef.nl)^0)^0
+
+local name = m.C(m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0)
+
+local arrow = S * "<-"
+
+-- a defined name only have meaning in a given environment
+local Def = name * m.Carg(1)
+
+local num = m.C(m.R"09"^1) * S / tonumber
+
+local String = "'" * m.C((any - "'" - m.P"\n")^0) * expect("'", "MisTerm1")
+ + '"' * m.C((any - '"' - m.P"\n")^0) * expect('"', "MisTerm2")
+
+
+local defined = "%" * Def / function (c,Defs)
+ local cat = Defs and Defs[c] or Predef[c]
+ if not cat then
+ error("name '" .. c .. "' undefined")
+ end
+ return cat
+end
+
+local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R
+
+local item = defined + Range + m.C(any - m.P"\n")
+
+local Class =
+ "["
+ * (m.C(m.P"^"^-1)) -- optional complement symbol
+ * m.Cf(expect(item, "ExpItem") * (item - "]")^0, mt.__add)
+ / function (c, p) return c == "^" and any - p or p end
+ * expect("]", "MisClose8")
+
+local function adddef (t, k, exp)
+ if t[k] then
+ -- TODO 改了一下这里的代码,重复定义不会抛错
+ --error("'"..k.."' already defined as a rule")
+ else
+ t[k] = exp
+ end
+ return t
+end
+
+local function firstdef (n, r) return adddef({n}, n, r) end
+
+
+local function NT (n, b)
+ if not b then
+ error("rule '"..n.."' used outside a grammar")
+ else return mm.V(n)
+ end
+end
+
+
+local exp = m.P{ "Exp",
+ Exp = S * ( m.V"Grammar"
+ + m.Cf(m.V"Seq" * (S * "/" * expect(S * m.V"Seq", "ExpPatt1"))^0, mt.__add) );
+ Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix" * (S * m.V"Prefix")^0, mt.__mul);
+ Prefix = "&" * expect(S * m.V"Prefix", "ExpPatt2") / mt.__len
+ + "!" * expect(S * m.V"Prefix", "ExpPatt3") / mt.__unm
+ + m.V"Suffix";
+ Suffix = m.Cf(m.V"Primary" *
+ ( S * ( m.P"+" * m.Cc(1, mt.__pow)
+ + m.P"*" * m.Cc(0, mt.__pow)
+ + m.P"?" * m.Cc(-1, mt.__pow)
+ + "^" * expect( m.Cg(num * m.Cc(mult))
+ + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow)
+ + name * m.Cc"lab"
+ ),
+ "ExpNumName")
+ + "->" * expect(S * ( m.Cg((String + num) * m.Cc(mt.__div))
+ + m.P"{}" * m.Cc(nil, m.Ct)
+ + m.Cg(Def / getdef * m.Cc(mt.__div))
+ ),
+ "ExpCap")
+ + "=>" * expect(S * m.Cg(Def / getdef * m.Cc(m.Cmt)),
+ "ExpName1")
+ )
+ )^0, function (a,b,f) if f == "lab" then return a + mm.T(b) else return f(a,b) end end );
+ Primary = "(" * expect(m.V"Exp", "ExpPatt4") * expect(S * ")", "MisClose1")
+ + String / mm.P
+ + Class
+ + defined
+ + "%" * expect(m.P"{", "ExpNameOrLab")
+ * expect(S * m.V"Label", "ExpLab1")
+ * expect(S * "}", "MisClose7") / mm.T
+ + "{:" * (name * ":" + m.Cc(nil)) * expect(m.V"Exp", "ExpPatt5")
+ * expect(S * ":}", "MisClose2")
+ / function (n, p) return mm.Cg(p, n) end
+ + "=" * expect(name, "ExpName2")
+ / function (n) return mm.Cmt(mm.Cb(n), equalcap) end
+ + m.P"{}" / mm.Cp
+ + "{~" * expect(m.V"Exp", "ExpPatt6")
+ * expect(S * "~}", "MisClose3") / mm.Cs
+ + "{|" * expect(m.V"Exp", "ExpPatt7")
+ * expect(S * "|}", "MisClose4") / mm.Ct
+ + "{" * expect(m.V"Exp", "ExpPattOrClose")
+ * expect(S * "}", "MisClose5") / mm.C
+ + m.P"." * m.Cc(any)
+ + (name * -arrow + "<" * expect(name, "ExpName3")
+ * expect(">", "MisClose6")) * m.Cb("G") / NT;
+ Label = num + name;
+ Definition = name * arrow * expect(m.V"Exp", "ExpPatt8");
+ Grammar = m.Cg(m.Cc(true), "G")
+ * m.Cf(m.V"Definition" / firstdef * (S * m.Cg(m.V"Definition"))^0,
+ adddef) / mm.P;
+}
+
+local pattern = S * m.Cg(m.Cc(false), "G") * expect(exp, "NoPatt") / mm.P
+ * S * expect(-any, "ExtraChars")
+
+local function lineno (s, i)
+ if i == 1 then return 1, 1 end
+ local adjustment = 0
+ -- report the current line if at end of line, not the next
+ if s:sub(i,i) == '\n' then
+ i = i-1
+ adjustment = 1
+ end
+ local rest, num = s:sub(1,i):gsub("[^\n]*\n", "")
+ local r = #rest
+ return 1 + num, (r ~= 0 and r or 1) + adjustment
+end
+
+local function calcline (s, i)
+ if i == 1 then return 1, 1 end
+ local rest, line = s:sub(1,i):gsub("[^\n]*\n", "")
+ local col = #rest
+ return 1 + line, col ~= 0 and col or 1
+end
+
+
+local function splitlines(str)
+ local t = {}
+ local function helper(line) tinsert(t, line) return "" end
+ helper((str:gsub("(.-)\r?\n", helper)))
+ return t
+end
+
+local function compile (p, defs)
+ if mm.type(p) == "pattern" then return p end -- already compiled
+ p = p .. " " -- for better reporting of column numbers in errors when at EOF
+ local ok, cp, label, poserr = pcall(function() return pattern:match(p, 1, defs) end)
+ if not ok and cp then
+ if type(cp) == "string" then
+ cp = cp:gsub("^[^:]+:[^:]+: ", "")
+ end
+ error(cp, 3)
+ end
+ if not cp then
+ local lines = splitlines(p)
+ local line, col = lineno(p, poserr)
+ local err = {}
+ tinsert(err, "L" .. line .. ":C" .. col .. ": " .. errinfo[label])
+ tinsert(err, lines[line])
+ tinsert(err, rep(" ", col-1) .. "^")
+ error("syntax error(s) in pattern\n" .. concat(err, "\n"), 3)
+ end
+ return cp
+end
+
+local function match (s, p, i)
+ local cp = mem[p]
+ if not cp then
+ cp = compile(p)
+ mem[p] = cp
+ end
+ return cp:match(s, i or 1)
+end
+
+local function find (s, p, i)
+ local cp = fmem[p]
+ if not cp then
+ cp = compile(p) / 0
+ cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) }
+ fmem[p] = cp
+ end
+ local i, e = cp:match(s, i or 1)
+ if i then return i, e - 1
+ else return i
+ end
+end
+
+local function gsub (s, p, rep)
+ local g = gmem[p] or {} -- ensure gmem[p] is not collected while here
+ gmem[p] = g
+ local cp = g[rep]
+ if not cp then
+ cp = compile(p)
+ cp = mm.Cs((cp / rep + 1)^0)
+ g[rep] = cp
+ end
+ return cp:match(s)
+end
+
+
+-- exported names
+local re = {
+ compile = compile,
+ match = match,
+ find = find,
+ gsub = gsub,
+ updatelocale = updatelocale,
+ calcline = calcline
+}
+
+if version == "Lua 5.1" then _G.re = re end
+
+return re
diff --git a/server/src/parser/split.lua b/server/src/parser/split.lua
new file mode 100644
index 00000000..6ce4a4e7
--- /dev/null
+++ b/server/src/parser/split.lua
@@ -0,0 +1,9 @@
+local m = require 'lpeglabel'
+
+local NL = m.P'\r\n' + m.S'\r\n'
+local LINE = m.C(1 - NL)
+
+return function (str)
+ local MATCH = m.Ct((LINE * NL)^0 * LINE)
+ return MATCH:match(str)
+end
diff --git a/server/src/service.lua b/server/src/service.lua
new file mode 100644
index 00000000..46a9ca37
--- /dev/null
+++ b/server/src/service.lua
@@ -0,0 +1,52 @@
+local sleep = require 'ffi.sleep'
+local ext = require 'process.ext'
+local lsp = require 'lsp'
+local Method= require 'method'
+
+local function listen(self, input, output)
+ if input then
+ log.info('指定输入文件,路径为:', input)
+ fs.create_directories(input:parent_path())
+ io.input(io.open(input:string(), 'rb'))
+ else
+ ext.set_filemode(io.stdin, 'b')
+ end
+ if output then
+ log.info('指定输出文件,路径为:', output)
+ fs.create_directories(output:parent_path())
+ io.output(io.open(output:string(), 'wb'))
+ else
+ ext.set_filemode(io.stdout, 'b')
+ end
+ io.output():setvbuf 'no'
+
+ local session = lsp()
+ session:setInput(function (mode)
+ return io.read(mode)
+ end)
+ session:setOutput(function (buf)
+ io.write(buf)
+ end)
+ session:start(function (method, params)
+ local f = Method[method]
+ if f then
+ local suc, res, res2 = pcall(f, session, params)
+ if suc then
+ return res, res2
+ else
+ return nil, '发生运行时错误:' .. res
+ end
+ end
+ return nil, '没有注册方法:' .. method
+ end)
+end
+
+local mt = {
+ listen = listen,
+}
+mt.__index = mt
+
+return function ()
+ local session = setmetatable({}, mt)
+ return session
+end
diff --git a/server/src/utility/init.lua b/server/src/utility/init.lua
new file mode 100644
index 00000000..55bd6b0d
--- /dev/null
+++ b/server/src/utility/init.lua
@@ -0,0 +1,2 @@
+require 'utility.io'
+require 'utility.table'
diff --git a/server/src/utility/io.lua b/server/src/utility/io.lua
new file mode 100644
index 00000000..73c65237
--- /dev/null
+++ b/server/src/utility/io.lua
@@ -0,0 +1,21 @@
+function io.load(file_path)
+ local f, e = io.open(file_path:string(), 'rb')
+ if not f then
+ return nil, e
+ end
+ local buf = f:read 'a'
+ f:close()
+ return buf
+end
+
+function io.save(file_path, content)
+ local f, e = io.open(file_path:string(), "wb")
+
+ if f then
+ f:write(content)
+ f:close()
+ return true
+ else
+ return false, e
+ end
+end
diff --git a/server/src/utility/table.lua b/server/src/utility/table.lua
new file mode 100644
index 00000000..37a74632
--- /dev/null
+++ b/server/src/utility/table.lua
@@ -0,0 +1,62 @@
+local table_sort = table.sort
+local string_rep = string.rep
+local type = type
+local pairs = pairs
+local ipairs = ipairs
+local math_type = math.type
+
+local TAB = setmetatable({}, { __index = function (self, n)
+ self[n] = string_rep('\t', n)
+ return self[n]
+end})
+
+local KEY = {}
+
+function table.dump(tbl)
+ if type(tbl) ~= 'table' then
+ error('必须是表')
+ end
+ local table_mark = {}
+ local lines = {}
+ lines[#lines+1] = '{'
+ local function unpack(tbl, tab)
+ if table_mark[tbl] then
+ error('不能循环引用')
+ end
+ table_mark[tbl] = true
+ local keys = {}
+ for key in pairs(tbl) do
+ if type(key) == 'string' then
+ if key:find('[^%w_]') then
+ KEY[key] = ('[%q]'):format(key)
+ else
+ KEY[key] = key
+ end
+ elseif math_type(key) == 'integer' then
+ KEY[key] = ('[%d]'):format(key)
+ else
+ error('必须使用字符串或整数作为键')
+ end
+ keys[#keys+1] = key
+ end
+ table_sort(keys, function (a, b)
+ return KEY[a] < KEY[b]
+ end)
+ for _, key in ipairs(keys) do
+ local value = tbl[key]
+ local tp = type(value)
+ if tp == 'table' then
+ lines[#lines+1] = ('%s%s = {'):format(TAB[tab+1], KEY[key])
+ unpack(value, tab+1)
+ lines[#lines+1] = ('%s},'):format(TAB[tab+1])
+ elseif tp == 'string' or tp == 'number' or tp == 'boolean' then
+ lines[#lines+1] = ('%s%s = %q,'):format(TAB[tab+1], KEY[key], value)
+ else
+ error(('不支持的值类型[%s]'):format(tp))
+ end
+ end
+ end
+ unpack(tbl, 0)
+ lines[#lines+1] = '}'
+ return table.concat(lines, '\n')
+end
diff --git a/server/test/definition/arg.lua b/server/test/definition/arg.lua
new file mode 100644
index 00000000..2004d666
--- /dev/null
+++ b/server/test/definition/arg.lua
@@ -0,0 +1,23 @@
+TEST [[
+local function xx (<!xx!>)
+ <?xx?> = 1
+end
+]]
+
+TEST [[
+local function x (x, <!...!>)
+ x = <?...?>
+end
+]]
+
+TEST [[
+function mt<!:!>x()
+ <?self?> = 1
+end
+]]
+
+TEST [[
+function mt:x(<!self!>)
+ <?self?> = 1
+end
+]]
diff --git a/server/test/definition/bug.lua b/server/test/definition/bug.lua
new file mode 100644
index 00000000..b0e890ca
--- /dev/null
+++ b/server/test/definition/bug.lua
@@ -0,0 +1,15 @@
+TEST [[
+local <!x!>
+function _(x)
+end
+function _()
+ <?x?>
+end
+]]
+
+TEST [[
+function _(<!x!>)
+ do return end
+ <?x?> = 1
+end
+]]
diff --git a/server/test/definition/function.lua b/server/test/definition/function.lua
new file mode 100644
index 00000000..1ef6a463
--- /dev/null
+++ b/server/test/definition/function.lua
@@ -0,0 +1,24 @@
+
+TEST [[
+function <!x!> () end
+<?x?> = 1
+]]
+
+TEST [[
+local function <!x!> () end
+<?x?> = 1
+]]
+
+TEST [[
+local x
+local function <!x!> ()
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+function x()
+end
+<?x?> = 1
+]]
diff --git a/server/test/definition/init.lua b/server/test/definition/init.lua
new file mode 100644
index 00000000..9bd7a1d8
--- /dev/null
+++ b/server/test/definition/init.lua
@@ -0,0 +1,22 @@
+local matcher = require 'matcher'
+
+rawset(_G, 'TEST', true)
+
+function TEST(script)
+ local start = script:find('<!', 1, true) + 2
+ local finish = script:find('!>', 1, true) - 1
+ local pos = script:find('<?', 1, true) + 2
+ local new_script = script:gsub('<[!?]', ' '):gsub('[!?]>', ' ')
+
+ local suc, a, b = matcher.definition(new_script, pos)
+ assert(suc)
+ assert(a == start)
+ assert(b == finish)
+end
+
+require 'definition.set'
+require 'definition.local'
+require 'definition.arg'
+require 'definition.function'
+--require 'definition.table'
+require 'definition.bug'
diff --git a/server/test/definition/local.lua b/server/test/definition/local.lua
new file mode 100644
index 00000000..0737443d
--- /dev/null
+++ b/server/test/definition/local.lua
@@ -0,0 +1,191 @@
+TEST [[
+local <!x!>
+<?x?> = 1
+]]
+
+TEST [[
+local z, y, <!x!>
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!> = 1
+<?x?> = 1
+]]
+
+TEST [[
+local z, y, <!x!> = 1
+<?x?> = 1
+]]
+
+TEST [[
+local x
+local <!x!>
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+do
+ local x
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+if <?x?> then
+ local x
+end
+]]
+
+TEST [[
+local <!x!>
+if x then
+ local x
+elseif <?x?> then
+ local x
+end
+]]
+
+TEST [[
+local <!x!>
+if x then
+ local x
+elseif x then
+ local x
+else
+ local x
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+if x then
+ <?x?> = 1
+elseif x then
+ local x
+else
+ local x
+end
+]]
+
+TEST [[
+local <!x!>
+for x = 1, 10 do
+end
+<?x?> = 1
+]]
+
+TEST [[
+local x
+for <!x!> = 1, 10 do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+for x in x do
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+for x in <?x?> do
+end
+]]
+
+TEST [[
+local x
+for <!x!> in x do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local x
+for z, y, <!x!> in x do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+while <?x?> do
+end
+]]
+
+TEST [[
+local <!x!>
+while x do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+while x do
+ local x
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+repeat
+ <?x?> = 1
+until true
+]]
+
+TEST [[
+local <!x!>
+repeat
+ local x
+until true
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+repeat
+until <?x?>
+]]
+
+TEST [[
+local x
+repeat
+ local <!x!>
+until <?x?>
+]]
+
+TEST [[
+local <!x!>
+function _()
+ local x
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+return function ()
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+local x = function ()
+ <?x?> = 1
+end
+]]
diff --git a/server/test/definition/set.lua b/server/test/definition/set.lua
new file mode 100644
index 00000000..2e48e490
--- /dev/null
+++ b/server/test/definition/set.lua
@@ -0,0 +1,30 @@
+TEST [[
+<!x!> = 1
+<?x?> = 1
+]]
+
+TEST [[
+do
+ <!global!> = 1
+end
+<?global?> = 1
+]]
+
+TEST [[
+<!x!> = 1
+do
+ local x = 1
+end
+<?x?> = 1
+]]
+
+TEST [[
+x = 1
+do
+ local <!x!> = 1
+ do
+ x = 2
+ end
+ <?x?> = 1
+end
+]]
diff --git a/server/test/definition/table.lua b/server/test/definition/table.lua
new file mode 100644
index 00000000..13a3b555
--- /dev/null
+++ b/server/test/definition/table.lua
@@ -0,0 +1,6 @@
+TEST [[
+local t = {
+ <!x!> = 1,
+}
+t.<?x?> = 1
+]]
diff --git a/server/test/implementation/arg.lua b/server/test/implementation/arg.lua
new file mode 100644
index 00000000..2004d666
--- /dev/null
+++ b/server/test/implementation/arg.lua
@@ -0,0 +1,23 @@
+TEST [[
+local function xx (<!xx!>)
+ <?xx?> = 1
+end
+]]
+
+TEST [[
+local function x (x, <!...!>)
+ x = <?...?>
+end
+]]
+
+TEST [[
+function mt<!:!>x()
+ <?self?> = 1
+end
+]]
+
+TEST [[
+function mt:x(<!self!>)
+ <?self?> = 1
+end
+]]
diff --git a/server/test/implementation/bug.lua b/server/test/implementation/bug.lua
new file mode 100644
index 00000000..b0e890ca
--- /dev/null
+++ b/server/test/implementation/bug.lua
@@ -0,0 +1,15 @@
+TEST [[
+local <!x!>
+function _(x)
+end
+function _()
+ <?x?>
+end
+]]
+
+TEST [[
+function _(<!x!>)
+ do return end
+ <?x?> = 1
+end
+]]
diff --git a/server/test/implementation/function.lua b/server/test/implementation/function.lua
new file mode 100644
index 00000000..90b75da8
--- /dev/null
+++ b/server/test/implementation/function.lua
@@ -0,0 +1,24 @@
+
+TEST [[
+function <!x!> () end
+<?x?> = 1
+]]
+
+TEST [[
+local function <!x!> () end
+<?x?> = 1
+]]
+
+TEST [[
+local x
+local function <!x!> ()
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local x
+function <!x!>()
+end
+<?x?> = 1
+]]
diff --git a/server/test/implementation/if.lua b/server/test/implementation/if.lua
new file mode 100644
index 00000000..0da8be1a
--- /dev/null
+++ b/server/test/implementation/if.lua
@@ -0,0 +1,106 @@
+TEST [[
+<!x!> = 1
+if 1 then
+ x = 1
+else
+ <?x?> = 1
+end
+]]
+
+TEST [[
+<!x!> = 1
+if 1 then
+ <!x!> = 1
+else
+ <!x!> = 1
+end
+<?x?> = 1
+]]
+
+TEST [[
+<!x!> = 1
+if 1 then
+ <!x!> = 1
+elseif 1 then
+ <!x!> = 1
+else
+ <!x!> = 1
+end
+<?x?> = 1
+]]
+
+TEST [[
+<!x!> = 1
+if 1 then
+ <!x!> = 1
+elseif 1 then
+ <!x!> = 1
+ if 1 then
+ <!x!> = 1
+ end
+else
+ <!x!> = 1
+end
+<?x?> = 1
+]]
+
+TEST [[
+<!x!> = 1
+while true do
+ <!x!> = 1
+end
+<?x?> = 1
+]]
+
+TEST [[
+<!x!> = 1
+for _ in _ do
+ <!x!> = 1
+end
+<?x?> = 1
+]]
+
+TEST [[
+<!x!> = 1
+for _ = 1, 1 do
+ <!x!> = 1
+end
+<?x?> = 1
+]]
+
+TEST [[
+x3 = 1
+repeat
+ <!x3!> = 1
+until <?x3?> == 1
+]]
+
+TEST [[
+<!x!> = 1
+repeat
+ <!x!> = 1
+until 1
+<?x?> = 1
+]]
+
+TEST [[
+<!x!> = 1
+while 1 do
+ x = 1
+ <!x!> = 1
+end
+<?x?> = 1
+]]
+
+TEST [[
+<!x!> = 1
+if 1 then
+ if 1 then
+ x = 1
+ end
+else
+ if 1 then
+ <?x?> = 1
+ end
+end
+]]
diff --git a/server/test/implementation/init.lua b/server/test/implementation/init.lua
new file mode 100644
index 00000000..de8d9d63
--- /dev/null
+++ b/server/test/implementation/init.lua
@@ -0,0 +1,57 @@
+local matcher = require 'matcher'
+
+rawset(_G, 'TEST', true)
+
+local function catch_target(script)
+ local list = {}
+ local cur = 1
+ while true do
+ local start, finish = script:find('<!.-!>', cur)
+ if not start then
+ break
+ end
+ list[#list+1] = { start + 2, finish - 2 }
+ cur = finish + 1
+ end
+ return list
+end
+
+local function founded(targets, results)
+ while true do
+ local target = table.remove(targets)
+ if not target then
+ break
+ end
+ for i, result in ipairs(results) do
+ if target[1] == result[1] and target[2] == result[2] then
+ table.remove(results, i)
+ goto CONTINUE
+ end
+ end
+ do return false end
+ ::CONTINUE::
+ end
+ if #results == 0 then
+ return true
+ else
+ return false
+ end
+end
+
+function TEST(script)
+ local target = catch_target(script)
+ local pos = script:find('<?', 1, true) + 2
+ local new_script = script:gsub('<[!?]', ' '):gsub('[!?]>', ' ')
+
+ local suc, result = matcher.implementation(new_script, pos)
+ assert(suc)
+ assert(founded(target, result))
+end
+
+require 'implementation.set'
+require 'implementation.local'
+require 'implementation.arg'
+require 'implementation.function'
+require 'implementation.if'
+--require 'implementation.table'
+require 'implementation.bug'
diff --git a/server/test/implementation/local.lua b/server/test/implementation/local.lua
new file mode 100644
index 00000000..7e9b3db0
--- /dev/null
+++ b/server/test/implementation/local.lua
@@ -0,0 +1,191 @@
+TEST [[
+local <!x!>
+<?x?> = 1
+]]
+
+TEST [[
+local z, y, <!x!>
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!> = 1
+<?x?> = 1
+]]
+
+TEST [[
+local z, y, <!x!> = 1
+<?x?> = 1
+]]
+
+TEST [[
+local x
+local <!x!>
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+do
+ local x
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+if <?x?> then
+ local x
+end
+]]
+
+TEST [[
+local <!x2!>
+if x2 then
+ local x2
+elseif <?x2?> then
+ local x2
+end
+]]
+
+TEST [[
+local <!x!>
+if x then
+ local x
+elseif x then
+ local x
+else
+ local x
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+if x then
+ <?x?> = 1
+elseif x then
+ local x
+else
+ local x
+end
+]]
+
+TEST [[
+local <!x!>
+for x = 1, 10 do
+end
+<?x?> = 1
+]]
+
+TEST [[
+local x
+for <!x!> = 1, 10 do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+for x in x do
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+for x in <?x?> do
+end
+]]
+
+TEST [[
+local x
+for <!x!> in x do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local x
+for z, y, <!x!> in x do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+while <?x?> do
+end
+]]
+
+TEST [[
+local <!x!>
+while x do
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+while x do
+ local x
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+repeat
+ <?x?> = 1
+until true
+]]
+
+TEST [[
+local <!x!>
+repeat
+ local x
+until true
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+repeat
+until <?x?>
+]]
+
+TEST [[
+local x
+repeat
+ local <!x!>
+until <?x?>
+]]
+
+TEST [[
+local <!x!>
+function _()
+ local x
+end
+<?x?> = 1
+]]
+
+TEST [[
+local <!x!>
+return function ()
+ <?x?> = 1
+end
+]]
+
+TEST [[
+local <!x!>
+local x = function ()
+ <?x?> = 1
+end
+]]
diff --git a/server/test/implementation/set.lua b/server/test/implementation/set.lua
new file mode 100644
index 00000000..5c4a1a2e
--- /dev/null
+++ b/server/test/implementation/set.lua
@@ -0,0 +1,31 @@
+TEST [[
+<!x!> = 1
+<?x?> = 1
+]]
+
+TEST [[
+global = 1
+do
+ <!global!> = 2
+end
+<?global?> = 3
+]]
+
+TEST [[
+<!x!> = 1
+do
+ local x = 1
+end
+<?x?> = 1
+]]
+
+TEST [[
+x = 1
+do
+ local x = 1
+ do
+ <!x!> = 2
+ end
+ <?x?> = 1
+end
+]]
diff --git a/server/test/implementation/table.lua b/server/test/implementation/table.lua
new file mode 100644
index 00000000..13a3b555
--- /dev/null
+++ b/server/test/implementation/table.lua
@@ -0,0 +1,6 @@
+TEST [[
+local t = {
+ <!x!> = 1,
+}
+t.<?x?> = 1
+]]
diff --git a/server/test/main.lua b/server/test/main.lua
new file mode 100644
index 00000000..c33526c6
--- /dev/null
+++ b/server/test/main.lua
@@ -0,0 +1,31 @@
+require 'filesystem'
+ROOT = fs.current_path()
+package.path = (ROOT / 'src' / '?.lua'):string()
+ .. ';' .. (ROOT / 'src' / '?' / 'init.lua'):string()
+ .. ';' .. (ROOT / 'test' / '?.lua'):string()
+ .. ';' .. (ROOT / 'test' / '?' / 'init.lua'):string()
+
+log = require 'log'
+log.init(ROOT, ROOT / 'log' / 'test.log')
+log.debug('测试开始')
+
+require 'utility'
+require 'global_protect'
+
+local function main()
+ local function test(name)
+ local clock = os.clock()
+ print(('测试[%s]...'):format(name))
+ require(name)
+ print(('测试[%s]用时[%.3f]'):format(name, os.clock() - clock))
+ end
+
+ test 'definition'
+ test 'implementation'
+
+ print('测试完成')
+end
+
+main()
+
+log.debug('测试完成')