1- local files = require ' files'
21local searchCode = require ' LuaJIT.searchCode'
32local cdefRerence = require ' LuaJIT.cdefRerence'
43local cdriver = require ' LuaJIT.c-parser.cdriver'
54local util = require ' utility'
5+ local SDBMHash = require ' SDBMHash'
66
77local namespace <const> = ' ffi.namespace*.'
88
9+ local function nkeys (t )
10+ local n = 0
11+ for key , value in pairs (t ) do
12+ n = n + 1
13+ end
14+ return n
15+ end
16+
17+ local function isSingleNode (ast )
18+ if type (ast ) ~= ' table' then
19+ return false
20+ end
21+ local len = # ast
22+ return len == 1 and len == nkeys (ast )
23+ end
24+
925-- TODO:supprot 32bit ffi, need config
1026local knownTypes = {
1127 [" bool" ] = ' boolean' ,
@@ -93,7 +109,7 @@ function builder:getType(name)
93109 local isStruct
94110 for _ , n in ipairs (name ) do
95111 if type (n ) == ' table' then
96- n = n .name
112+ n = n .full_name
97113 end
98114 if not isStruct then
99115 isStruct = self :needDeref (self :getTypeAst (n ))
@@ -127,7 +143,7 @@ function builder:isVoid(ast)
127143 return self :isVoid (self :getTypeAst (typename ))
128144end
129145
130- function builder :buildStructOrUnion (lines , ast , tt , name )
146+ function builder :buildStructOrUnion (lines , tt , name )
131147 lines [# lines + 1 ] = ' ---@class ' .. self :getType (name )
132148 for _ , field in ipairs (tt .fields or {}) do
133149 if field .name and field .type then
@@ -136,7 +152,7 @@ function builder:buildStructOrUnion(lines, ast, tt, name)
136152 end
137153end
138154
139- function builder :buildFunction (lines , ast , tt , name )
155+ function builder :buildFunction (lines , tt , name )
140156 local param_names = {}
141157 for i , param in ipairs (tt .params or {}) do
142158 lines [# lines + 1 ] = (' ---@param %s %s' ):format (param .name , self :getType (param .type ))
@@ -153,18 +169,98 @@ function builder:buildFunction(lines, ast, tt, name)
153169 lines [# lines + 1 ] = (' function m.%s(%s) end' ):format (name , table.concat (param_names , ' , ' ))
154170end
155171
156- function builder :buildTypedef (lines , ast , tt , name )
172+ function builder :buildTypedef (lines , tt , name )
157173 local def = tt .def [1 ]
158174 if type (def ) == ' table' and not def .name then
159175 -- 这个时候没有主类型,只有一个别名,直接创建一个别名结构体
160- self .switch_ast (def .type , self , lines , def , def , name )
176+ self .switch_ast (def .type , self , lines , def , name )
161177 else
162178 lines [# lines + 1 ] = (' ---@alias %s %s' ):format (name , self :getType (def ))
163179 end
164180end
165181
166- function builder :buildEnum (lines , ast , tt , name )
167- -- TODO
182+ local calculate
183+
184+ local function binop (enumer , val , fn )
185+ local e1 , e2 = calculate (enumer , val [1 ]), calculate (enumer , val [2 ])
186+ if type (e1 ) == " number" and type (e2 ) == " number" then
187+ return fn (e1 , e2 )
188+ else
189+ return { e1 , e2 , op = val .op }
190+ end
191+ end
192+ do
193+ local ops = {
194+ [' +' ] = function (a , b ) return a + b end ,
195+ [' -' ] = function (a , b ) return a - b end ,
196+ [' *' ] = function (a , b ) return a * b end ,
197+ [' /' ] = function (a , b ) return a / b end ,
198+ [' &' ] = function (a , b ) return a & b end ,
199+ [' |' ] = function (a , b ) return a | b end ,
200+ [' ~' ] = function (a , b )
201+ if not b then
202+ return ~a
203+ end
204+ return a ~ b
205+ end ,
206+ [' <<' ] = function (a , b ) return a << b end ,
207+ [' >>' ] = function (a , b ) return a >> b end ,
208+ }
209+ calculate = function (enumer , val )
210+ if ops [val .op ] then
211+ return binop (enumer , val , ops [val .op ])
212+ end
213+ if isSingleNode (val ) then
214+ val = val [1 ]
215+ end
216+ if type (val ) == " string" then
217+ if enumer [val ] then
218+ return enumer [val ]
219+ end
220+ return tonumber (val )
221+ end
222+ return val
223+ end
224+ end
225+
226+ local function pushEnumValue (enumer , name , v )
227+ if isSingleNode (v ) then
228+ v = tonumber (v [1 ])
229+ end
230+ enumer [name ] = v
231+ enumer [# enumer + 1 ] = v
232+ return v
233+ end
234+
235+ function builder :buildEnum (lines , tt , name )
236+ local enumer = {}
237+ for i , val in ipairs (tt .values ) do
238+ local name = val .name
239+ local v = val .value
240+ if not v then
241+ if i == 1 then
242+ v = 0
243+ else
244+ v = tt .values [i - 1 ].realValue + 1
245+ end
246+ end
247+ if type (v ) == ' table' and v .op then
248+ v = calculate (enumer , v )
249+ end
250+ if v then
251+ val .realValue = pushEnumValue (enumer , name , v )
252+ end
253+ end
254+ local alias = {}
255+ for k , v in pairs (enumer ) do
256+ alias [# alias + 1 ] = type (k ) == ' number' and v or ([[ '%s']] ):format (k )
257+ if type (k ) ~= ' number' then
258+ lines [# lines + 1 ] = (' m.%s = %s' ):format (k , v )
259+ end
260+ end
261+ if name then
262+ lines [# lines + 1 ] = (' ---@alias %s %s' ):format (self :getType (name ), table.concat (alias , ' | ' ))
263+ end
168264end
169265
170266builder .switch_ast
@@ -178,37 +274,60 @@ builder.switch_ast
178274 :case ' typedef'
179275 :call (builder .buildTypedef )
180276
181-
277+ local function stringStartsWith (self , searchString , position )
278+ if position == nil or position < 0 then
279+ position = 0
280+ end
281+ return string.sub (self , position + 1 , # searchString + position ) == searchString
282+ end
182283local firstline = (' ---@meta \n ---@class %s \n local %s = {}' ):format (namespace , constName )
183284local m = {}
285+ local function compileCode (lines , asts , b )
286+ for _ , ast in ipairs (asts ) do
287+ local tt = ast .type
288+
289+ if tt .type == ' enum' and not stringStartsWith (ast .name , ' enum@' ) then
290+ goto continue
291+ end
292+ if not tt .name then
293+ if tt .type ~= ' enum' then
294+ goto continue
295+ end
296+ -- 匿名枚举也要创建具体的值
297+ lines = lines or { firstline }
298+ builder .switch_ast (tt .type , b , lines , tt )
299+ else
300+ tt .full_name = ast .name
301+ lines = lines or { firstline }
302+ builder .switch_ast (tt .type , b , lines , tt , tt .full_name )
303+ lines [# lines + 1 ] = ' \n '
304+ end
305+ :: continue::
306+ end
307+ return lines
308+ end
184309function m .compileCodes (codes )
185310 --- @class ffi.builder
186- local b = setmetatable ({ globalAsts = {} }, { __index = builder })
311+ local b = setmetatable ({ globalAsts = {}, cacheEnums = {} }, { __index = builder })
187312
188313 local lines
189314 for _ , code in ipairs (codes ) do
190315 local asts = cdriver .process_context (code )
191316 if not asts then
192317 goto continue
193318 end
194- lines = lines or { firstline }
195319 table.insert (b .globalAsts , asts )
196- for _ , ast in ipairs (asts ) do
197- local tt = ast .type
198- if tt .name then
199- tt .name = ast .name
200- builder .switch_ast (tt .type , b , lines , ast , tt , tt .name )
201- lines [# lines + 1 ] = ' \n '
202- end
203- end
320+ lines = compileCode (lines , asts , b )
204321 :: continue::
205322 end
206323 return lines
207324end
208325
209- --- @async
210- files .watch (function (ev , uri )
211- if ev == ' compile' then
326+ function m .initBuilder ()
327+ local config = require ' config'
328+ local fs = require ' bee.filesystem'
329+ --- @async
330+ return function (uri )
212331 local refs = cdefRerence ()
213332 if not refs or # refs == 0 then
214333 return
@@ -218,8 +337,19 @@ files.watch(function (ev, uri)
218337 if not codes then
219338 return
220339 end
221- local res = m .compileCodes (codes )
340+
341+ local texts = m .compileCodes (codes )
342+ if not texts then
343+ return
344+ end
345+
346+ local hash = (' %08x' ):format (SDBMHash ():hash (uri ))
347+ local encoding = config .get (nil , ' Lua.runtime.fileEncoding' )
348+ local filePath = METAPATH .. ' /ffi/' .. table.concat ({ hash , encoding }, ' _' )
349+
350+ fs .create_directories (fs .path (filePath ):parent_path ())
351+ util .saveFile (filePath .. ' .d.lua' , table.concat (texts , ' \n ' ))
222352 end
223- end )
353+ end
224354
225355return m
0 commit comments