diff --git a/Cargo.lock b/Cargo.lock index b93399c..e8f35be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -102,6 +108,15 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "emmylua_parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349c1d30c8de98867496e5b600d1f710656047830a8f1d5aae38e4ac7ee2a252" +dependencies = [ + "rowan", +] + [[package]] name = "encoding_rs" version = "0.8.34" @@ -179,6 +194,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -312,11 +333,13 @@ name = "luals-basic" version = "0.1.0" dependencies = [ "cc", + "emmylua_parser", "encoding_rs", "glob", "lazy_static", "mlua", "notify", + "rowan", "tokio", ] @@ -370,7 +393,7 @@ dependencies = [ "mlua-sys", "num-traits", "parking_lot", - "rustc-hash", + "rustc-hash 2.0.0", ] [[package]] @@ -492,12 +515,30 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "rowan" +version = "0.15.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a542b0253fa46e632d27a1dc5cf7b930de4df8659dc6e720b647fc72147ae3d" +dependencies = [ + "countme", + "hashbrown", + "rustc-hash 1.1.0", + "text-size", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.0.0" @@ -603,6 +644,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "text-size" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" + [[package]] name = "tokio" version = "1.40.0" diff --git a/Cargo.toml b/Cargo.toml index 4fac9c2..9129ecb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,5 @@ lazy_static = "1.4.0" encoding_rs = "0.8" tokio = { version = "1.40.0", features = ["full"] } notify = { version = "6.1.1", features = ["serde"] } +emmylua_parser = "0.3" +rowan = { version = "0.15.16" } \ No newline at end of file diff --git a/crates/basic/Cargo.toml b/crates/basic/Cargo.toml index 56da7eb..6cc19d5 100644 --- a/crates/basic/Cargo.toml +++ b/crates/basic/Cargo.toml @@ -9,7 +9,8 @@ lazy_static.workspace = true encoding_rs.workspace = true tokio.workspace = true notify.workspace = true - +emmylua_parser.workspace = true +rowan.workspace = true [build-dependencies] cc = "1.0" diff --git a/crates/basic/src/lib.rs b/crates/basic/src/lib.rs index 1e76d2e..0a0e815 100644 --- a/crates/basic/src/lib.rs +++ b/crates/basic/src/lib.rs @@ -3,6 +3,7 @@ mod codestyle; mod lpeglabel; mod lua_seri; mod override_lua; +mod parser; #[macro_use] extern crate lazy_static; @@ -20,11 +21,12 @@ pub fn lua_preload(lua: &Lua) -> LuaResult<()> { // codestyle codestyle::register_code_format_module(lua)?; + parser::register_parser_module(lua)?; + add_package_path( &lua, vec![ "resources/?.lua;resources/?/init.lua;", - // "resources/override_script/?.lua;resources/override_script/?/init.lua;", "resources/script/?.lua;resources/script/?/init.lua", ], )?; diff --git a/crates/basic/src/parser/lua_node.rs b/crates/basic/src/parser/lua_node.rs new file mode 100644 index 0000000..384e175 --- /dev/null +++ b/crates/basic/src/parser/lua_node.rs @@ -0,0 +1,157 @@ +use emmylua_parser::{LuaSyntaxKind, LuaSyntaxNode, LuaSyntaxToken, LuaTokenKind}; +use mlua::prelude::*; + +pub enum LuaNodeWrapper { + Node(LuaSyntaxNode), + Token(LuaSyntaxToken), +} + +impl LuaNodeWrapper { + pub fn new(node: LuaSyntaxNode) -> Self { + Self::Node(node) + } + + pub fn from_node_or_token( + node_or_token: rowan::NodeOrToken, + ) -> Self { + match node_or_token { + rowan::NodeOrToken::Node(node) => Self::Node(node), + rowan::NodeOrToken::Token(token) => Self::Token(token), + } + } +} + +impl LuaUserData for LuaNodeWrapper { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("isNode", |_, this| match this { + LuaNodeWrapper::Node(_) => Ok(true), + LuaNodeWrapper::Token(_) => Ok(false), + }); + fields.add_field_method_get("isToken", |_, this| match this { + LuaNodeWrapper::Node(_) => Ok(false), + LuaNodeWrapper::Token(_) => Ok(true), + }); + fields.add_field_method_get("kind", |_, this| match this { + LuaNodeWrapper::Node(node) => { + let kind: LuaSyntaxKind = node.kind().into(); + Ok(kind as u16) + } + LuaNodeWrapper::Token(token) => { + let kind: LuaTokenKind = token.kind().into(); + Ok(kind as u16) + } + }); + fields.add_field_method_get("kindText", |_, this| { + let text = match this { + LuaNodeWrapper::Node(node) => { + let kind: LuaSyntaxKind = node.kind().into(); + format!("{:?}", kind) + } + LuaNodeWrapper::Token(token) => { + let kind: LuaTokenKind = token.kind().into(); + format!("{:?}", kind) + } + }; + + Ok(text) + }); + } + + fn add_methods>(methods: &mut M) { + methods.add_method("getText", |_, this, ()| { + let text = match this { + LuaNodeWrapper::Node(node) => node.text().to_string(), + LuaNodeWrapper::Token(token) => token.text().to_string(), + }; + Ok(text) + }); + methods.add_method("getRange", |lua, this, ()| { + let range = match this { + LuaNodeWrapper::Node(node) => node.text_range(), + LuaNodeWrapper::Token(token) => token.text_range(), + }; + let table = lua.create_table()?; + let line: u32 = range.start().into(); + let col: u32 = range.start().into(); + table.set("start", line)?; + table.set("end", col)?; + Ok(table) + }); + methods.add_method("getChildren", |lua, this, ()| { + let children = match this { + LuaNodeWrapper::Node(node) => node + .children_with_tokens() + .filter_map(|it| match it { + rowan::NodeOrToken::Node(node) => Some(LuaNodeWrapper::Node(node)), + rowan::NodeOrToken::Token(token) => Some(LuaNodeWrapper::Token(token)), + }) + .collect(), + LuaNodeWrapper::Token(_) => vec![], + }; + + Ok(children) + }); + + methods.add_method("dump", |_, this, ()| { + let dump = match this { + LuaNodeWrapper::Node(node) => format!("{:#?}", node), + LuaNodeWrapper::Token(token) => format!("{:#?}", token), + }; + Ok(dump) + }); + + methods.add_method("getParent", |_, this, ()| { + let parent = match this { + LuaNodeWrapper::Node(node) => node.parent().map(LuaNodeWrapper::Node), + LuaNodeWrapper::Token(token) => token.parent().map(LuaNodeWrapper::Node), + }; + Ok(parent) + }); + + methods.add_method("getPrevSibling", |_, this, ()| { + let prev_sibling = match this { + LuaNodeWrapper::Node(node) => node.prev_sibling().map(LuaNodeWrapper::Node), + LuaNodeWrapper::Token(token) => match token.prev_sibling_or_token() { + Some(rowan::NodeOrToken::Node(node)) => Some(LuaNodeWrapper::Node(node)), + Some(rowan::NodeOrToken::Token(token)) => Some(LuaNodeWrapper::Token(token)), + None => None, + }, + }; + Ok(prev_sibling) + }); + + methods.add_method("getNextSibling", |_, this, ()| { + let next_sibling = match this { + LuaNodeWrapper::Node(node) => node.next_sibling().map(LuaNodeWrapper::Node), + LuaNodeWrapper::Token(token) => match token.next_sibling_or_token() { + Some(rowan::NodeOrToken::Node(node)) => Some(LuaNodeWrapper::Node(node)), + Some(rowan::NodeOrToken::Token(token)) => Some(LuaNodeWrapper::Token(token)), + None => None, + }, + }; + Ok(next_sibling) + }); + + methods.add_method("getDescendants", |_, this, ()| { + let descendants = match this { + LuaNodeWrapper::Node(node) => node + .descendants_with_tokens() + .map(LuaNodeWrapper::from_node_or_token) + .collect(), + LuaNodeWrapper::Token(_) => vec![], + }; + Ok(descendants) + }); + + methods.add_method("getAncestors", |_, this, ()| { + let ancestors: Vec = match this { + LuaNodeWrapper::Node(node) => node.ancestors().map(LuaNodeWrapper::Node).collect(), + LuaNodeWrapper::Token(token) => { + token.parent_ancestors().map(LuaNodeWrapper::Node).collect() + } + }; + + Ok(ancestors) + }); + } +} diff --git a/crates/basic/src/parser/lua_parser.rs b/crates/basic/src/parser/lua_parser.rs new file mode 100644 index 0000000..913bcc9 --- /dev/null +++ b/crates/basic/src/parser/lua_parser.rs @@ -0,0 +1,16 @@ +use emmylua_parser::{LuaParser, ParserConfig}; +use mlua::{prelude::*, Lua}; + +use super::lua_syntax_tree::LuaSyntaxTree; + +fn parse(_: &Lua, text: String) -> LuaResult { + let tree = LuaParser::parse(&text, ParserConfig::default()); + + Ok(LuaSyntaxTree::new(tree)) +} + +pub fn lua_parser(lua: &Lua) -> LuaResult { + let parser = lua.create_table()?; + parser.set("parse", lua.create_function(parse)?)?; + Ok(parser) +} diff --git a/crates/basic/src/parser/lua_syntax_tree.rs b/crates/basic/src/parser/lua_syntax_tree.rs new file mode 100644 index 0000000..5c385fc --- /dev/null +++ b/crates/basic/src/parser/lua_syntax_tree.rs @@ -0,0 +1,49 @@ +use emmylua_parser::LuaSyntaxTree as EmmyLuaSyntaxTree; +use mlua::prelude::*; +use rowan::TextSize; + +use super::lua_node::LuaNodeWrapper; + +pub struct LuaSyntaxTree { + tree: EmmyLuaSyntaxTree, +} + +impl LuaSyntaxTree { + pub fn new(tree: EmmyLuaSyntaxTree) -> Self { + Self { tree } + } + + pub fn get_root(&self) -> LuaNodeWrapper { + LuaNodeWrapper::new(self.tree.get_red_root().clone()) + } + + pub fn get_line_col(&self, offset: usize) -> Option<(usize, usize)> { + let offset = TextSize::from(offset as u32); + let (line, col) = self.tree.get_line_col(offset)?; + Some((line, col)) + } + + pub fn get_offset(&self, line: usize, col: usize) -> Option { + let offset = self.tree.get_offset(line, col)?; + Some(offset.into()) + } +} + +impl LuaUserData for LuaSyntaxTree { + fn add_fields>(fields: &mut F) {} + + fn add_methods>(methods: &mut M) { + methods.add_method("getRoot", |_, this, ()| Ok(this.get_root())); + // methods.add_method("get_chunk_node", |_, this, ()| Ok(this.get_chunk_node())); + methods.add_method("getLineCol", |lua, this, offset: usize| { + let (line, col) = this.get_line_col(offset).unwrap(); + let table = lua.create_table()?; + table.set(1, line)?; + table.set(2, col)?; + Ok(table) + }); + methods.add_method("getOffset", |_, this, (line, col): (usize, usize)| { + Ok(this.get_offset(line, col)) + }); + } +} diff --git a/crates/basic/src/parser/mod.rs b/crates/basic/src/parser/mod.rs new file mode 100644 index 0000000..8ce0331 --- /dev/null +++ b/crates/basic/src/parser/mod.rs @@ -0,0 +1,16 @@ +mod lua_parser; +mod lua_syntax_tree; +mod lua_node; + +use mlua::{prelude::*, Lua}; + +use crate::add_preload_module; + + +pub fn register_parser_module(lua: &Lua) -> LuaResult<()> { + // lua.parser + let lua_parser_loader = + lua.create_function(|lua: &Lua, ()| Ok(lua_parser::lua_parser(lua)))?; + add_preload_module(&lua, "lua.parser", lua_parser_loader)?; + Ok(()) +} diff --git a/resources/main.lua b/resources/main.lua index e3b5efc..b40531d 100644 --- a/resources/main.lua +++ b/resources/main.lua @@ -2,7 +2,7 @@ local fs = require 'bee.filesystem' local util = require 'utility' local version = require 'version' require 'config.env' - +require 'test' local function getValue(value) if value == 'true' or value == nil then value = true diff --git a/resources/test.lua b/resources/test.lua index 6915972..883d767 100644 --- a/resources/test.lua +++ b/resources/test.lua @@ -1,119 +1,13 @@ -package.path = package.path - .. ';./test/?.lua' - .. ';./test/?/init.lua' -local fs = require 'bee.filesystem' --- local rootPath = fs.exe_path():parent_path():parent_path():string() -ROOT = fs.current_path() -TEST = true -DEVELOP = true ---FOOTPRINT = true ---TRACE = true -LOGPATH = LOGPATH or (ROOT:string() .. '/log') -METAPATH = METAPATH or (ROOT:string() .. '/meta') +local parser = require "lua.parser" -collectgarbage 'generational' +local t = [[ +local t = 123 +-- hhihih +]] ----@diagnostic disable-next-line: duplicate-set-field -io.write = function () end - ----@diagnostic disable-next-line: lowercase-global -log = require 'log' -log.init(ROOT, ROOT / 'log' / 'test.log') -log.debug('测试开始') - -LOCALE = 'zh-cn' - ---dofile((ROOT / 'build_package.lua'):string()) -require 'tracy' - -local function loadAllLibs() - assert(require 'bee.filesystem') - assert(require 'bee.subprocess') - assert(require 'bee.thread') - assert(require 'bee.socket') - assert(require 'lpeglabel') -end - -local function test(name) - local clock = os.clock() - print(('测试[%s]...'):format(name)) - local originRequire = require - require = function (n) - local v, p = originRequire(n) - if p and p:find 'test/' then - package.loaded[n] = nil - end - return v, p - end - require(name) - require = originRequire - print(('测试[%s]用时[%.3f]'):format(name, os.clock() - clock)) -end - -local function testAll() - test 'basic' - test 'definition' - test 'type_inference' - test 'implementation' - test 'references' - test 'hover' - test 'completion' - test 'diagnostics' - test 'crossfile' - test 'highlight' - test 'rename' - test 'signature' - test 'command' - test 'document_symbol' - test 'code_action' - test 'other' -end - -local files = require "files" - -local function main() - require 'utility'.enableCloseFunction() - require 'client' .client 'VSCode' - - local lclient = require 'lclient' - local ws = require 'workspace' - local furi = require 'file-uri' - require 'vm' - - --log.print = true - - TESTROOT = ROOT:string() .. '/test_root/' - TESTROOTURI = furi.encode(TESTROOT) - TESTURI = furi.encode(TESTROOT .. 'unittest.lua') - - ---@async - lclient():start(function (client) - client:registerFakers() - local rootUri = furi.encode(TESTROOT) - client:initialize { - rootUri = rootUri, - } - - ws.awaitReady(rootUri) - - print('Loaded files in', os) - for uri in files.eachFile() do - print(uri) - end - print('===============') - - testAll() - end) - - test 'tclient' - test 'full' - test 'plugins.test' - test 'cli.test' +local t = parser.parse(t) +local root = t:getRoot() +for i, child in pairs(root:getDescendants()) do + print(i, child.kindText, child:getText()) end -loadAllLibs() -main() - -log.debug('test finish.') -require 'bee.thread'.sleep(1000) -os.exit()