diff options
author | CppCXY <812125110@qq.com> | 2024-02-22 20:29:13 +0800 |
---|---|---|
committer | CppCXY <812125110@qq.com> | 2024-02-22 20:29:13 +0800 |
commit | 9b6df71d97a70ee7179949ef9f15368cbf29dcbd (patch) | |
tree | bf7a7e62ed7c164a12bdce437c17262a5235bcec /script/plugins/ffi/init.lua | |
parent | 483fe246b6ae8c25d433aa15e43f04f0e71a74d5 (diff) | |
parent | 3e6fd3ce1f2f0528336ded939d776a29bbfaf2eb (diff) | |
download | lua-language-server-9b6df71d97a70ee7179949ef9f15368cbf29dcbd.zip |
Merge branch 'master' of github.com:CppCXY/lua-language-server
Diffstat (limited to 'script/plugins/ffi/init.lua')
-rw-r--r-- | script/plugins/ffi/init.lua | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/script/plugins/ffi/init.lua b/script/plugins/ffi/init.lua new file mode 100644 index 00000000..17159ff2 --- /dev/null +++ b/script/plugins/ffi/init.lua @@ -0,0 +1,374 @@ +local cdriver = require 'plugins.ffi.c-parser.cdriver' +local util = require 'plugins.ffi.c-parser.util' +local utility = require 'utility' +local SDBMHash = require 'SDBMHash' +local config = require 'config' +local fs = require 'bee.filesystem' +local ws = require 'workspace' +local furi = require 'file-uri' + +local namespace <const> = 'ffi.namespace*.' + +--TODO:supprot 32bit ffi, need config +local knownTypes = { + ["bool"] = 'boolean', + ["char"] = 'integer', + ["short"] = 'integer', + ["int"] = 'integer', + ["long"] = 'integer', + ["float"] = 'number', + ["double"] = 'number', + ["signed"] = 'integer', + ["__signed"] = 'integer', + ["__signed__"] = 'integer', + ["unsigned"] = 'integer', + ["ptrdiff_t"] = 'integer', + ["size_t"] = 'integer', + ["ssize_t"] = 'integer', + ["wchar_t"] = 'integer', + ["int8_t"] = 'integer', + ["int16_t"] = 'integer', + ["int32_t"] = 'integer', + ["int64_t"] = 'integer', + ["uint8_t"] = 'integer', + ["uint16_t"] = 'integer', + ["uint32_t"] = 'integer', + ["uint64_t"] = 'integer', + ["intptr_t"] = 'integer', + ["uintptr_t"] = 'integer', + ["__int8"] = 'integer', + ["__int16"] = 'integer', + ["__int32"] = 'integer', + ["__int64"] = 'integer', + ["_Bool"] = 'boolean', + ["__ptr32"] = 'integer', + ["__ptr64"] = 'integer', + --[[ + ["_Complex"] = 1, + ["complex"] = 1, + ["__complex"] = 1, + ["__complex__"] = 1, +]] + ["unsignedchar"] = 'integer', + ["unsignedshort"] = 'integer', + ["unsignedint"] = 'integer', + ["unsignedlong"] = 'integer', + ["signedchar"] = 'integer', + ["signedshort"] = 'integer', + ["signedint"] = 'integer', + ["signedlong"] = 'integer', +} + +local blackKeyWord <const> = { + ['and'] = "_and", + ['do'] = "_do", + ['elseif'] = "_elseif", + ['end'] = "_end", + ['false'] = "_false", + ['function'] = "_function", + ['in'] = "_in", + ['local'] = "_local", + ['nil'] = "_nil", + ['not'] = "_not", + ['or'] = "_or", + ['repeat'] = "_repeat", + ['then'] = "_then", + ['true'] = "_true", +} + +local invaildKeyWord <const> = { + const = true, + restrict = true, + volatile = true, +} + +local constName <const> = 'm' + +---@class ffi.builder +local builder = { switch_ast = utility.switch() } + +function builder:getTypeAst(name) + for i, asts in ipairs(self.globalAsts) do + if asts[name] then + return asts[name] + end + end +end + +function builder:needDeref(ast) + if not ast then + return false + end + if ast.type == 'typedef' then + -- maybe no name + ast = ast.def[1] + if type(ast) ~= 'table' then + return self:needDeref(self:getTypeAst(ast)) + end + end + if ast.type == 'struct' or ast.type == 'union' then + return true + else + return false + end +end + +function builder:getType(name) + if type(name) == 'table' then + local t = "" + local isStruct + if name.type then + t = t .. name.type .. "@" + name = name.name + end + for _, n in ipairs(name) do + if type(n) == 'table' then + n = n.full_name + end + if invaildKeyWord[n] then + goto continue + end + if not isStruct then + isStruct = self:needDeref(self:getTypeAst(n)) + end + t = t .. n + ::continue:: + end + -- deref 一级指针 + if isStruct and t:sub(#t) == '*' then + t = t:sub(1, #t - 1) + end + name = t + end + if knownTypes[name] then + return knownTypes[name] + end + return namespace .. name +end + +function builder:isVoid(ast) + if not ast then + return false + end + if ast.type == 'typedef' then + return self:isVoid(self:getTypeAst(ast.def[1]) or ast.def[1]) + end + + local typename = type(ast.type) == 'table' and ast.type[1] or ast + if typename == 'void' then + return true + end + return self:isVoid(self:getTypeAst(typename)) +end + +local function getArrayType(arr) + if type(arr) ~= "table" then + return arr and '[]' or '' + end + local res = '' + for i, v in ipairs(arr) do + res = res .. '[]' + end + return res +end + +local function getValidName(name) + return blackKeyWord[name] or name +end + +function builder:buildStructOrUnion(lines, tt, name) + lines[#lines+1] = '---@class ' .. self:getType(name) + for _, field in ipairs(tt.fields or {}) do + if field.name and field.type then + lines[#lines+1] = ('---@field %s %s%s'):format(getValidName(field.name), self:getType(field.type), + getArrayType(field.isarray)) + end + end +end + +function builder:buildFunction(lines, tt, name) + local param_names = {} + for i, param in ipairs(tt.params or {}) do + local param_name = getValidName(param.name) + lines[#lines+1] = ('---@param %s %s%s'):format(param_name, self:getType(param.type), getArrayType(param.idxs)) + param_names[#param_names+1] = param_name + end + if tt.vararg then + param_names[#param_names+1] = '...' + end + if tt.ret then + if not self:isVoid(tt.ret) then + lines[#lines+1] = ('---@return %s'):format(self:getType(tt.ret.type)) + end + end + lines[#lines+1] = ('function m.%s(%s) end'):format(name, table.concat(param_names, ', ')) +end + +function builder:buildTypedef(lines, tt, name) + local def = tt.def[1] + if type(def) == 'table' and not def.name then + -- 这个时候没有主类型,只有一个别名,直接创建一个别名结构体 + self.switch_ast(def.type, self, lines, def, name) + else + lines[#lines+1] = ('---@alias %s %s'):format(self:getType(name), self:getType(def)) + end +end + +local calculate + +local function binop(enumer, val, fn) + local e1, e2 = calculate(enumer, val[1]), calculate(enumer, val[2]) + if type(e1) == "number" and type(e2) == "number" then + return fn(e1, e2) + else + return { e1, e2, op = val.op } + end +end +do + local ops = { + ['+'] = function (a, b) return a + b end, + ['-'] = function (a, b) return a - b end, + ['*'] = function (a, b) return a * b end, + ['/'] = function (a, b) return a / b end, + ['&'] = function (a, b) return a & b end, + ['|'] = function (a, b) return a | b end, + ['~'] = function (a, b) + if not b then + return ~a + end + return a ~ b + end, + ['<<'] = function (a, b) return a << b end, + ['>>'] = function (a, b) return a >> b end, + } + calculate = function (enumer, val) + if ops[val.op] then + return binop(enumer, val, ops[val.op]) + end + val = util.expandSingle(val) + if type(val) == "string" then + if enumer[val] then + return enumer[val] + end + return tonumber(val) + end + return val + end +end + +local function pushEnumValue(enumer, name, v) + v = tonumber(util.expandSingle(v)) + enumer[name] = v + enumer[#enumer+1] = v + return v +end + +function builder:buildEnum(lines, tt, name) + local enumer = {} + for i, val in ipairs(tt.values) do + local name = val.name + local v = val.value + if not v then + if i == 1 then + v = 0 + else + v = tt.values[i - 1].realValue + 1 + end + end + if type(v) == 'table' and v.op then + v = calculate(enumer, v) + end + if v then + val.realValue = pushEnumValue(enumer, name, v) + end + end + local alias = {} + for k, v in pairs(enumer) do + alias[#alias+1] = type(k) == 'number' and v or ([['%s']]):format(k) + if type(k) ~= 'number' then + lines[#lines+1] = ('m.%s = %s'):format(k, v) + end + end + if name then + lines[#lines+1] = ('---@alias %s %s'):format(self:getType(name), table.concat(alias, ' | ')) + end +end + +builder.switch_ast + :case 'struct' + :case 'union' + :call(builder.buildStructOrUnion) + :case 'enum' + :call(builder.buildEnum) + : case 'function' + :call(builder.buildFunction) + :case 'typedef' + :call(builder.buildTypedef) + +local function stringStartsWith(self, searchString, position) + if position == nil or position < 0 then + position = 0 + end + return string.sub(self, position + 1, #searchString + position) == searchString +end +local firstline = ('---@meta \n ---@class %s \n local %s = {}'):format(namespace, constName) +local m = {} +local function compileCode(lines, asts, b) + for _, ast in ipairs(asts) do + local tt = ast.type + + if tt.type == 'enum' and not stringStartsWith(ast.name, 'enum@') then + goto continue + end + if not tt.name then + if tt.type ~= 'enum' then + goto continue + end + --匿名枚举也要创建具体的值 + lines = lines or { firstline } + builder.switch_ast(tt.type, b, lines, tt) + else + tt.full_name = ast.name + lines = lines or { firstline } + builder.switch_ast(tt.type, b, lines, tt, tt.full_name) + lines[#lines+1] = '\n' + end + ::continue:: + end + return lines +end +function m.compileCodes(codes) + ---@class ffi.builder + local b = setmetatable({ globalAsts = {}, cacheEnums = {} }, { __index = builder }) + + local lines + for _, code in ipairs(codes) do + local asts = cdriver.process_context(code) + if not asts then + goto continue + end + table.insert(b.globalAsts, asts) + lines = compileCode(lines, asts, b) + ::continue:: + end + return lines +end + +function m.build_single(codes, fileDir, uri) + local texts = m.compileCodes(codes) + if not texts then + return + end + local fullPath = fileDir /ws.getRelativePath(uri) + + if fullPath:stem():string():find '%.' then + local newPath = fullPath:parent_path() / (fullPath:stem():string():gsub('%.', '/') .. ".lua") + fs.create_directories(newPath:parent_path()) + fullPath = newPath + end + + utility.saveFile(tostring(fullPath), table.concat(texts, '\n')) + return true +end + +return m |