tl.lua 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. local function mkdir(path)
  2. if lovr.system.getOS() == 'Windows' then
  3. os.execute('mkdir ' .. path:gsub('/', '\\'))
  4. else
  5. os.execute('mkdir -p ' .. path)
  6. end
  7. end
  8. local function writeFile(path, contents)
  9. local file = assert(io.open(path, 'w'))
  10. file:write(contents)
  11. file:close()
  12. end
  13. local function trim(s)
  14. return (s:gsub('^%s+', ''):gsub('%s+$', ''))
  15. end
  16. local function splitUnion(typeStr)
  17. local parts = {}
  18. local depth = 0
  19. local start = 1
  20. for i = 1, #typeStr do
  21. local c = typeStr:sub(i, i)
  22. if c == '{' or c == '(' or c == '<' then
  23. depth = depth + 1
  24. elseif c == '}' or c == ')' or c == '>' then
  25. depth = depth - 1
  26. elseif c == '|' and depth == 0 then
  27. table.insert(parts, trim(typeStr:sub(start, i - 1)))
  28. start = i + 1
  29. end
  30. end
  31. table.insert(parts, trim(typeStr:sub(start)))
  32. return parts
  33. end
  34. local function stripOuterParens(typeStr)
  35. local s = trim(typeStr)
  36. if s:sub(1, 1) ~= '(' or s:sub(-1) ~= ')' then
  37. return s
  38. end
  39. local depth = 0
  40. for i = 1, #s do
  41. local c = s:sub(i, i)
  42. if c == '(' then
  43. depth = depth + 1
  44. elseif c == ')' then
  45. depth = depth - 1
  46. if depth == 0 and i < #s then
  47. return s
  48. end
  49. end
  50. end
  51. if depth == 0 then
  52. return trim(s:sub(2, -2))
  53. end
  54. return s
  55. end
  56. local function capitalize(part)
  57. if part == '' then return '' end
  58. return part:sub(1, 1):upper() .. part:sub(2)
  59. end
  60. local function normalizeContextPart(part)
  61. local name = ''
  62. local s = tostring(part or '')
  63. for word in s:gmatch('%w+') do
  64. name = name .. capitalize(word)
  65. end
  66. if name == '' then
  67. name = 'Value'
  68. end
  69. return name
  70. end
  71. local function makeTypeName(base, used)
  72. local parts = {}
  73. for part in base:gmatch('[%w]+') do
  74. parts[#parts + 1] = capitalize(part)
  75. end
  76. local name = table.concat(parts)
  77. if name == '' then name = 'Type' end
  78. if name:match('^%d') then
  79. name = 'T' .. name
  80. end
  81. local suffix = 2
  82. local unique = name
  83. while used[unique] do
  84. unique = name .. suffix
  85. suffix = suffix + 1
  86. end
  87. used[unique] = true
  88. return unique
  89. end
  90. return function(api)
  91. local preamble = {}
  92. local enumDefs = {}
  93. local objectDefs = {}
  94. local moduleDefs = {}
  95. local lovrDefs = {}
  96. local lovrNestedDefs = {}
  97. local moduleNestedDefs = {}
  98. local tableTypeDefs = {}
  99. local enums = {}
  100. local objects = {}
  101. local modules = {}
  102. local callbacks = api.callbacks or {}
  103. local objectNames = {}
  104. local enumNames = {}
  105. local recordNames = {}
  106. local usedTypeNames = {}
  107. for _, module in ipairs(api.modules) do
  108. for _, enum in ipairs(module.enums) do
  109. enumNames[enum.name] = true
  110. usedTypeNames[enum.name] = true
  111. table.insert(enums, enum)
  112. end
  113. for _, object in ipairs(module.objects) do
  114. objectNames[object.name] = true
  115. recordNames[object.name] = true
  116. usedTypeNames[object.name] = true
  117. table.insert(objects, object)
  118. end
  119. table.insert(modules, module)
  120. end
  121. local objectByName = {}
  122. for _, object in ipairs(objects) do
  123. objectByName[object.name] = object
  124. end
  125. local function collectObjectMethods(object)
  126. local list = {}
  127. local indexByName = {}
  128. local visited = {}
  129. local function visit(obj)
  130. if not obj or visited[obj.name] then
  131. return
  132. end
  133. visited[obj.name] = true
  134. if obj.extends then
  135. visit(objectByName[obj.extends])
  136. end
  137. for _, method in ipairs(obj.methods or {}) do
  138. local idx = indexByName[method.name]
  139. if idx then
  140. list[idx] = method
  141. else
  142. indexByName[method.name] = #list + 1
  143. list[#list + 1] = method
  144. end
  145. end
  146. end
  147. visit(object)
  148. return list
  149. end
  150. local function addLovrNestedRecord(name, fields)
  151. lovrNestedDefs[#lovrNestedDefs + 1] = ' record ' .. name
  152. for _, field in ipairs(fields) do
  153. lovrNestedDefs[#lovrNestedDefs + 1] = ' ' .. field
  154. end
  155. lovrNestedDefs[#lovrNestedDefs + 1] = ' end\n'
  156. end
  157. local function makeLovrNestedName(base)
  158. local baseName = capitalize(base or '')
  159. if baseName == '' then baseName = 'Type' end
  160. local qualified = 'lovr.' .. baseName
  161. local unique = qualified
  162. local suffix = 2
  163. while usedTypeNames[unique] do
  164. baseName = capitalize(base or '') .. suffix
  165. unique = 'lovr.' .. baseName
  166. suffix = suffix + 1
  167. end
  168. usedTypeNames[unique] = true
  169. return unique, baseName
  170. end
  171. local function addModuleNestedRecord(moduleType, name, fields)
  172. local list = moduleNestedDefs[moduleType]
  173. if not list then
  174. list = {}
  175. moduleNestedDefs[moduleType] = list
  176. end
  177. list[#list + 1] = ' record ' .. name
  178. for _, field in ipairs(fields) do
  179. list[#list + 1] = ' ' .. field
  180. end
  181. list[#list + 1] = ' end\n'
  182. end
  183. local function makeModuleNestedName(moduleType, base)
  184. local root = normalizeContextPart(base)
  185. local baseName = root ~= '' and root or 'Type'
  186. local qualified = moduleType .. '.' .. baseName
  187. local suffix = 2
  188. while usedTypeNames[qualified] do
  189. baseName = root .. suffix
  190. qualified = moduleType .. '.' .. baseName
  191. suffix = suffix + 1
  192. end
  193. usedTypeNames[qualified] = true
  194. return qualified, baseName
  195. end
  196. local function makeModuleContext(moduleType, functionName)
  197. return {
  198. nested = 'module',
  199. module = moduleType,
  200. base = normalizeContextPart(functionName or '')
  201. }
  202. end
  203. local function extendContext(context, fieldName)
  204. if type(context) == 'table' and context.base then
  205. local suffix = normalizeContextPart(fieldName)
  206. if context.nested == 'lovr' then
  207. return {
  208. base = context.base .. suffix,
  209. nested = 'lovr'
  210. }
  211. elseif context.nested == 'module' then
  212. return {
  213. base = context.base .. suffix,
  214. module = context.module,
  215. nested = 'module'
  216. }
  217. end
  218. end
  219. if type(context) == 'string' then
  220. if not fieldName or fieldName == '' then
  221. return context
  222. end
  223. return context .. '_' .. tostring(fieldName)
  224. end
  225. return fieldName or context
  226. end
  227. local needsWhere = {}
  228. local function collectUnionObjects(typeStr)
  229. if not typeStr or not typeStr:match('|') then return end
  230. local parts = splitUnion(typeStr)
  231. if #parts <= 1 then return end
  232. local hit = {}
  233. local count = 0
  234. for _, part in ipairs(parts) do
  235. if objectNames[part] then
  236. if not hit[part] then
  237. hit[part] = true
  238. count = count + 1
  239. end
  240. end
  241. end
  242. if count >= 2 then
  243. for name in pairs(hit) do
  244. needsWhere[name] = true
  245. end
  246. end
  247. end
  248. local function walkInfo(info)
  249. if not info then return end
  250. if info.type == 'function' then
  251. if info.arguments then
  252. for _, arg in ipairs(info.arguments) do
  253. walkInfo(arg)
  254. end
  255. end
  256. if info.returns then
  257. for _, ret in ipairs(info.returns) do
  258. walkInfo(ret)
  259. end
  260. end
  261. elseif info.type == 'table' and info.table then
  262. for _, field in ipairs(info.table) do
  263. walkInfo(field)
  264. end
  265. else
  266. collectUnionObjects(info.type)
  267. end
  268. end
  269. for _, module in ipairs(modules) do
  270. for _, fn in ipairs(module.functions) do
  271. for _, variant in ipairs(fn.variants) do
  272. for _, arg in ipairs(variant.arguments) do
  273. walkInfo(arg)
  274. end
  275. for _, ret in ipairs(variant.returns) do
  276. walkInfo(ret)
  277. end
  278. end
  279. end
  280. for _, object in ipairs(module.objects) do
  281. for _, method in ipairs(object.methods) do
  282. for _, variant in ipairs(method.variants) do
  283. for _, arg in ipairs(variant.arguments) do
  284. walkInfo(arg)
  285. end
  286. for _, ret in ipairs(variant.returns) do
  287. walkInfo(ret)
  288. end
  289. end
  290. end
  291. end
  292. end
  293. for _, callback in ipairs(callbacks) do
  294. for _, variant in ipairs(callback.variants) do
  295. for _, arg in ipairs(variant.arguments) do
  296. walkInfo(arg)
  297. end
  298. for _, ret in ipairs(variant.returns) do
  299. walkInfo(ret)
  300. end
  301. end
  302. end
  303. local hasTypeMethod = {}
  304. for _, object in ipairs(objects) do
  305. if object.name ~= 'Object' then
  306. for _, method in ipairs(collectObjectMethods(object)) do
  307. if method.name == 'type' then
  308. hasTypeMethod[object.name] = true
  309. break
  310. end
  311. end
  312. end
  313. end
  314. for name in pairs(hasTypeMethod) do
  315. needsWhere[name] = true
  316. end
  317. local tableTypes = {}
  318. local tableTypeForwards = {}
  319. local function tableShapeKey(tbl)
  320. local parts = {}
  321. for _, field in ipairs(tbl) do
  322. local key = field.name or ''
  323. local t = field.type or ''
  324. if field.table then
  325. t = t .. '(' .. tableShapeKey(field.table) .. ')'
  326. end
  327. parts[#parts + 1] = key .. ':' .. t
  328. end
  329. table.sort(parts)
  330. return table.concat(parts, ',')
  331. end
  332. local function isFunctionType(typeStr)
  333. local s = stripOuterParens(typeStr)
  334. return s == 'AnyFunction' or s:match('^function%s*%(') ~= nil
  335. end
  336. local function isTableType(typeStr)
  337. local s = stripOuterParens(typeStr)
  338. if s == 'table' then
  339. return true
  340. end
  341. if s:sub(1, 1) == '{' then
  342. return true
  343. end
  344. return recordNames[s] == true
  345. end
  346. local function normalizeUnionParts(parts)
  347. local seenParts = {}
  348. local seenNormalized = {}
  349. local normalized = {}
  350. local enumParts = {}
  351. local nonTableParts = {}
  352. local functionParts = {}
  353. local tableDisc = {}
  354. local tableNon = {}
  355. local hasAny = false
  356. local hasString = false
  357. local function addUnique(list, seen, value)
  358. if not seen[value] then
  359. seen[value] = true
  360. list[#list + 1] = value
  361. end
  362. end
  363. for _, raw in ipairs(parts) do
  364. local part = trim(raw)
  365. if part ~= '' then
  366. local s = stripOuterParens(part)
  367. if s == 'any' then
  368. hasAny = true
  369. elseif enumNames[s] then
  370. addUnique(enumParts, seenParts, s)
  371. elseif s == 'string' then
  372. hasString = true
  373. elseif isFunctionType(s) then
  374. addUnique(functionParts, seenParts, s)
  375. elseif isTableType(s) then
  376. if needsWhere[s] then
  377. addUnique(tableDisc, seenParts, s)
  378. else
  379. addUnique(tableNon, seenParts, s)
  380. end
  381. else
  382. addUnique(nonTableParts, seenParts, s)
  383. end
  384. end
  385. end
  386. if hasAny then
  387. return 'any'
  388. end
  389. if hasString then
  390. addUnique(normalized, seenNormalized, 'string')
  391. else
  392. if #enumParts == 1 then
  393. addUnique(normalized, seenNormalized, enumParts[1])
  394. elseif #enumParts > 1 then
  395. addUnique(normalized, seenNormalized, 'string')
  396. end
  397. end
  398. if #functionParts > 0 then
  399. addUnique(normalized, seenNormalized, 'AnyFunction')
  400. end
  401. local totalTables = #tableDisc + #tableNon
  402. if totalTables == 1 then
  403. if #tableNon == 1 then
  404. addUnique(normalized, seenNormalized, tableNon[1])
  405. else
  406. addUnique(normalized, seenNormalized, tableDisc[1])
  407. end
  408. elseif totalTables > 1 then
  409. if #tableNon > 0 then
  410. addUnique(normalized, seenNormalized, 'table')
  411. else
  412. for _, t in ipairs(tableDisc) do
  413. addUnique(normalized, seenNormalized, t)
  414. end
  415. end
  416. end
  417. for _, t in ipairs(nonTableParts) do
  418. addUnique(normalized, seenNormalized, t)
  419. end
  420. if #normalized == 0 then
  421. return 'any'
  422. elseif #normalized == 1 then
  423. return normalized[1]
  424. else
  425. return table.concat(normalized, ' | ')
  426. end
  427. end
  428. local function convertTypeString(typeStr)
  429. if not typeStr or typeStr == '' then return 'any' end
  430. typeStr = typeStr:gsub('%*', 'any')
  431. typeStr = typeStr:gsub('%f[%w]table%f[^%w]', 'table')
  432. typeStr = typeStr:gsub('%f[%w]function%f[^%w]', 'AnyFunction')
  433. typeStr = typeStr:gsub('%f[%w]lightuserdata%f[^%w]', 'userdata')
  434. typeStr = typeStr:gsub('%f[%w]vector%f[^%w]', 'Vec3')
  435. return normalizeUnionParts(splitUnion(typeStr))
  436. end
  437. local tableType
  438. local function isOptionalArg(arg)
  439. if arg.name and arg.name:sub(1, 3) == '...' then
  440. return false
  441. end
  442. if arg.type ~= 'table' then
  443. return arg.default ~= nil
  444. end
  445. if not arg.table then
  446. return arg.default ~= nil
  447. end
  448. for _, field in ipairs(arg.table) do
  449. if field.default == nil then
  450. return false
  451. end
  452. end
  453. return true
  454. end
  455. local function genInlineFunctionType(info, contextPrefix)
  456. if not info.arguments or not info.returns then
  457. return 'AnyFunction'
  458. end
  459. local args = {}
  460. local baseContext = contextPrefix or 'InlineFunction'
  461. local function wrapFunctionType(typeStr)
  462. if typeStr:match('^function') then
  463. return '(' .. typeStr .. ')'
  464. end
  465. return typeStr
  466. end
  467. for _, arg in ipairs(info.arguments) do
  468. local argType
  469. local argContext = extendContext(baseContext, arg.name or 'Arg')
  470. if arg.type == 'table' and arg.table then
  471. argType = tableType(arg.table, argContext)
  472. elseif arg.type == 'function' then
  473. argType = wrapFunctionType(genInlineFunctionType(arg, argContext))
  474. else
  475. argType = convertTypeString(arg.type)
  476. end
  477. if arg.name and arg.name:sub(1, 3) == '...' then
  478. args[#args + 1] = '...: ' .. argType
  479. else
  480. local suffix = isOptionalArg(arg) and '?' or ''
  481. local name = arg.name or '_'
  482. args[#args + 1] = ('%s%s: %s'):format(name, suffix, argType)
  483. end
  484. end
  485. local rets = {}
  486. for _, ret in ipairs(info.returns) do
  487. local retType
  488. local retContext = extendContext(baseContext, ret.name or 'Return')
  489. if ret.type == 'table' and ret.table then
  490. retType = tableType(ret.table, retContext)
  491. elseif ret.type == 'function' then
  492. retType = wrapFunctionType(genInlineFunctionType(ret, retContext))
  493. else
  494. retType = convertTypeString(ret.type)
  495. end
  496. if ret.name and ret.name:sub(1, 3) == '...' then
  497. rets[#rets + 1] = retType .. '...'
  498. else
  499. rets[#rets + 1] = retType
  500. end
  501. end
  502. local retlist
  503. if #rets == 0 then
  504. retlist = 'nil'
  505. elseif #rets == 1 then
  506. retlist = rets[1]
  507. else
  508. retlist = '(' .. table.concat(rets, ', ') .. ')'
  509. end
  510. return ('function(%s): %s'):format(table.concat(args, ', '), retlist)
  511. end
  512. local function emitLovrConf(tbl)
  513. local key = 'lovr.Conf:' .. tableShapeKey(tbl)
  514. if tableTypes[key] then
  515. return tableTypes[key]
  516. end
  517. local typeName = 'lovr.Conf'
  518. tableTypes[key] = typeName
  519. recordNames[typeName] = true
  520. usedTypeNames[typeName] = true
  521. lovrNestedDefs[#lovrNestedDefs + 1] = ' record Conf'
  522. -- nested records inside conf
  523. for _, field in ipairs(tbl) do
  524. if field.type == 'table' and field.table then
  525. local nestedName = capitalize(field.name)
  526. local nestedType = typeName .. '.' .. nestedName
  527. recordNames[nestedType] = true
  528. usedTypeNames[nestedType] = true
  529. lovrNestedDefs[#lovrNestedDefs + 1] = ' record ' .. nestedName
  530. for _, subfield in ipairs(field.table) do
  531. local subType
  532. if subfield.type == 'table' and subfield.table then
  533. subType = 'table'
  534. elseif subfield.type == 'function' then
  535. subType = genInlineFunctionType(subfield, 'Lovrconf' .. field.name .. (subfield.name or 'Field'))
  536. else
  537. subType = convertTypeString(subfield.type)
  538. end
  539. if subfield.default ~= nil and not subType:match('nil') then
  540. subType = subType .. ' | nil'
  541. end
  542. lovrNestedDefs[#lovrNestedDefs + 1] = (' %s: %s'):format(subfield.name, subType)
  543. end
  544. lovrNestedDefs[#lovrNestedDefs + 1] = ' end\n'
  545. end
  546. end
  547. -- fields of conf
  548. for _, field in ipairs(tbl) do
  549. local fieldType
  550. if field.type == 'table' and field.table then
  551. fieldType = typeName .. '.' .. capitalize(field.name)
  552. elseif field.type == 'function' then
  553. fieldType = genInlineFunctionType(field, 'Lovrconf' .. (field.name or 'Field'))
  554. else
  555. fieldType = convertTypeString(field.type)
  556. end
  557. if field.default ~= nil and not fieldType:match('nil') then
  558. fieldType = fieldType .. ' | nil'
  559. end
  560. lovrNestedDefs[#lovrNestedDefs + 1] = (' %s: %s'):format(field.name, fieldType)
  561. end
  562. lovrNestedDefs[#lovrNestedDefs + 1] = ' end\n'
  563. return typeName
  564. end
  565. tableType = function(tbl, context)
  566. context = context or 'Table'
  567. local key = tableShapeKey(tbl)
  568. local isLovrNested = type(context) == 'table' and context.nested == 'lovr'
  569. local isModuleNested = type(context) == 'table' and context.nested == 'module' and context.module
  570. if isLovrNested then
  571. if context.base == 'Conf' then
  572. return emitLovrConf(tbl)
  573. end
  574. key = 'lovr:' .. key
  575. elseif isModuleNested then
  576. key = ('module:%s:%s:%s'):format(context.module, context.base or '', key)
  577. end
  578. if tableTypes[key] then
  579. return tableTypes[key]
  580. end
  581. local contextBase = (isLovrNested or isModuleNested) and context.base or context
  582. local isArrayStruct = true
  583. local isTuple = true
  584. local isArrayTuple = true
  585. for _, field in ipairs(tbl) do
  586. if not (field.name and field.name:match('^%[%]%..+')) then
  587. isArrayStruct = false
  588. end
  589. if not (field.name and field.name:match('^%[%d+%]$')) then
  590. isTuple = false
  591. end
  592. if not (field.name and field.name:match('^%[%]%[%d+%]$')) then
  593. isArrayTuple = false
  594. end
  595. end
  596. if isArrayTuple then
  597. local items = {}
  598. table.sort(tbl, function(a, b)
  599. local ai = tonumber(a.name:match('%[%]%[(%d+)%]'))
  600. local bi = tonumber(b.name:match('%[%]%[(%d+)%]'))
  601. return ai < bi
  602. end)
  603. for _, field in ipairs(tbl) do
  604. local itemType
  605. if field.type == 'table' and field.table then
  606. local child = extendContext(context, field.name)
  607. itemType = tableType(field.table, child)
  608. elseif field.type == 'function' then
  609. itemType = genInlineFunctionType(field, extendContext(context, field.name))
  610. else
  611. itemType = convertTypeString(field.type)
  612. end
  613. items[#items + 1] = itemType
  614. end
  615. local tupleType = '{' .. table.concat(items, ', ') .. '}'
  616. local arrayType = '{' .. tupleType .. '}'
  617. tableTypes[key] = arrayType
  618. return arrayType
  619. end
  620. if isArrayStruct then
  621. local fields = {}
  622. for _, field in ipairs(tbl) do
  623. local name = field.name:sub(4)
  624. local fieldType
  625. if field.type == 'table' and field.table then
  626. local child = extendContext(context, name)
  627. fieldType = tableType(field.table, child)
  628. elseif field.type == 'function' then
  629. fieldType = genInlineFunctionType(field, extendContext(context, name))
  630. else
  631. fieldType = convertTypeString(field.type)
  632. end
  633. if field.default ~= nil and not fieldType:match('nil') then
  634. fieldType = fieldType .. ' | nil'
  635. end
  636. fields[#fields + 1] = { name = name, type = fieldType }
  637. end
  638. if isLovrNested then
  639. local qualified, baseName = makeLovrNestedName(contextBase .. 'Item')
  640. recordNames[qualified] = true
  641. tableTypes[key] = '{' .. qualified .. '}'
  642. local fieldLines = {}
  643. for _, field in ipairs(fields) do
  644. fieldLines[#fieldLines + 1] = ('%s: %s'):format(field.name, field.type)
  645. end
  646. addLovrNestedRecord(baseName, fieldLines)
  647. return tableTypes[key]
  648. elseif isModuleNested then
  649. local qualified, baseName = makeModuleNestedName(context.module, contextBase .. 'Item')
  650. recordNames[qualified] = true
  651. tableTypes[key] = '{' .. qualified .. '}'
  652. local fieldLines = {}
  653. for _, field in ipairs(fields) do
  654. fieldLines[#fieldLines + 1] = ('%s: %s'):format(field.name, field.type)
  655. end
  656. addModuleNestedRecord(context.module, baseName, fieldLines)
  657. return tableTypes[key]
  658. else
  659. local itemName = makeTypeName(contextBase .. 'Item', usedTypeNames)
  660. recordNames[itemName] = true
  661. tableTypeForwards[itemName] = true
  662. tableTypes[key] = '{' .. itemName .. '}'
  663. tableTypeDefs[#tableTypeDefs + 1] = 'global record ' .. itemName
  664. for _, field in ipairs(fields) do
  665. tableTypeDefs[#tableTypeDefs + 1] = (' %s: %s'):format(field.name, field.type)
  666. end
  667. tableTypeDefs[#tableTypeDefs + 1] = 'end\n'
  668. return tableTypes[key]
  669. end
  670. end
  671. if isTuple then
  672. local items = {}
  673. table.sort(tbl, function(a, b)
  674. local ai = tonumber(a.name:match('%[(%d+)%]'))
  675. local bi = tonumber(b.name:match('%[(%d+)%]'))
  676. return ai < bi
  677. end)
  678. for _, field in ipairs(tbl) do
  679. local itemType
  680. if field.type == 'table' and field.table then
  681. local child = extendContext(context, field.name)
  682. itemType = tableType(field.table, child)
  683. elseif field.type == 'function' then
  684. itemType = genInlineFunctionType(field, extendContext(context, field.name))
  685. else
  686. itemType = convertTypeString(field.type)
  687. end
  688. items[#items + 1] = itemType
  689. end
  690. local tupleType = '{' .. table.concat(items, ', ') .. '}'
  691. tableTypes[key] = tupleType
  692. return tupleType
  693. end
  694. local fields = {}
  695. for _, field in ipairs(tbl) do
  696. local fieldType
  697. if field.type == 'table' and field.table then
  698. local child = extendContext(context, field.name)
  699. fieldType = tableType(field.table, child)
  700. elseif field.type == 'function' then
  701. fieldType = genInlineFunctionType(field, extendContext(context, field.name))
  702. else
  703. fieldType = convertTypeString(field.type)
  704. end
  705. if field.default ~= nil and not fieldType:match('nil') then
  706. fieldType = fieldType .. ' | nil'
  707. end
  708. fields[#fields + 1] = { name = field.name, type = fieldType }
  709. end
  710. if isLovrNested then
  711. local qualified, baseName = makeLovrNestedName(contextBase)
  712. recordNames[qualified] = true
  713. tableTypes[key] = qualified
  714. local fieldLines = {}
  715. for _, field in ipairs(fields) do
  716. fieldLines[#fieldLines + 1] = ('%s: %s'):format(field.name, field.type)
  717. end
  718. addLovrNestedRecord(baseName, fieldLines)
  719. return qualified
  720. elseif isModuleNested then
  721. local qualified, baseName = makeModuleNestedName(context.module, contextBase)
  722. recordNames[qualified] = true
  723. tableTypes[key] = qualified
  724. local fieldLines = {}
  725. for _, field in ipairs(fields) do
  726. fieldLines[#fieldLines + 1] = ('%s: %s'):format(field.name, field.type)
  727. end
  728. addModuleNestedRecord(context.module, baseName, fieldLines)
  729. return qualified
  730. else
  731. local typeName = makeTypeName(contextBase, usedTypeNames)
  732. recordNames[typeName] = true
  733. tableTypeForwards[typeName] = true
  734. tableTypes[key] = typeName
  735. tableTypeDefs[#tableTypeDefs + 1] = 'global record ' .. typeName
  736. for _, field in ipairs(fields) do
  737. tableTypeDefs[#tableTypeDefs + 1] = (' %s: %s'):format(field.name, field.type)
  738. end
  739. tableTypeDefs[#tableTypeDefs + 1] = 'end\n'
  740. return typeName
  741. end
  742. end
  743. local function wrapFunctionType(typeStr)
  744. if typeStr:match('^function') then
  745. return '(' .. typeStr .. ')'
  746. end
  747. return typeStr
  748. end
  749. local function resolveInfoType(info, contextPrefix)
  750. if info.type == 'table' and info.table then
  751. if info.name == 't' and type(contextPrefix) == 'string' and contextPrefix:match('^Lovr_?conf') then
  752. return tableType(info.table, { base = 'Conf', nested = 'lovr' })
  753. end
  754. return tableType(info.table, contextPrefix)
  755. elseif info.type == 'function' then
  756. return genInlineFunctionType(info, contextPrefix)
  757. else
  758. return convertTypeString(info.type)
  759. end
  760. end
  761. local function genFunctionType(variant, selfType, contextPrefix)
  762. local prefix = contextPrefix or (selfType or 'Module')
  763. local args = {}
  764. if selfType then
  765. args[#args + 1] = 'self: ' .. selfType
  766. end
  767. for _, arg in ipairs(variant.arguments or {}) do
  768. local argContext = extendContext(prefix, arg.name or 'Arg')
  769. local argType = resolveInfoType(arg, argContext)
  770. argType = wrapFunctionType(argType)
  771. if arg.name and arg.name:sub(1, 3) == '...' then
  772. args[#args + 1] = '...: ' .. argType
  773. else
  774. local suffix = isOptionalArg(arg) and '?' or ''
  775. local name = arg.name or '_'
  776. args[#args + 1] = ('%s%s: %s'):format(name, suffix, argType)
  777. end
  778. end
  779. local rets = {}
  780. for _, ret in ipairs(variant.returns or {}) do
  781. local retContext = extendContext(prefix, ret.name or 'Return')
  782. local retType = resolveInfoType(ret, retContext)
  783. retType = wrapFunctionType(retType)
  784. if ret.name and ret.name:sub(1, 3) == '...' then
  785. rets[#rets + 1] = retType .. '...'
  786. else
  787. rets[#rets + 1] = retType
  788. end
  789. end
  790. local retlist
  791. if #rets == 0 then
  792. retlist = 'nil'
  793. elseif #rets == 1 then
  794. retlist = rets[1]
  795. else
  796. retlist = '(' .. table.concat(rets, ', ') .. ')'
  797. end
  798. return ('function(%s): %s'):format(table.concat(args, ', '), retlist)
  799. end
  800. -- Preamble
  801. preamble[#preamble + 1] = '-- Generated by `lovr api tl`'
  802. preamble[#preamble + 1] = ''
  803. preamble[#preamble + 1] = 'global type AnyFunction = function(...: any): any...'
  804. preamble[#preamble + 1] = ''
  805. if api then
  806. -- enums
  807. for _, enum in ipairs(enums) do
  808. enumDefs[#enumDefs + 1] = 'global enum ' .. enum.name
  809. for _, value in ipairs(enum.values) do
  810. enumDefs[#enumDefs + 1] = (' %q'):format(value.name)
  811. end
  812. enumDefs[#enumDefs + 1] = 'end\n'
  813. end
  814. -- object types
  815. for _, object in ipairs(objects) do
  816. objectDefs[#objectDefs + 1] = 'global record ' .. object.name .. ' is userdata'
  817. if needsWhere[object.name] then
  818. objectDefs[#objectDefs + 1] = (' where self:type() == %q'):format(object.name)
  819. end
  820. for _, method in ipairs(collectObjectMethods(object)) do
  821. for _, variant in ipairs(method.variants or {}) do
  822. objectDefs[#objectDefs + 1] = (' %s: %s'):format(method.name, genFunctionType(variant, object.name, extendContext(object.name, method.name)))
  823. end
  824. end
  825. objectDefs[#objectDefs + 1] = 'end\n'
  826. end
  827. -- module types
  828. local moduleInterfaces = { lovr = 'lovr' }
  829. local skipExternal = { utf8 = true }
  830. usedTypeNames['lovr'] = true
  831. recordNames['lovr'] = true
  832. for _, module in ipairs(modules) do
  833. if module.key ~= 'lovr' then
  834. if not (module.external and skipExternal[module.name]) then
  835. local moduleName = module.name
  836. local typeName
  837. if module.external then
  838. typeName = moduleName
  839. usedTypeNames[typeName] = true
  840. else
  841. typeName = makeTypeName('Lovr ' .. moduleName .. ' Module', usedTypeNames)
  842. end
  843. moduleInterfaces[module.key] = typeName
  844. recordNames[typeName] = true
  845. moduleDefs[#moduleDefs + 1] = 'global record ' .. typeName
  846. for _, fn in ipairs(module.functions) do
  847. for _, variant in ipairs(fn.variants or {}) do
  848. moduleDefs[#moduleDefs + 1] = (' %s: %s'):format(fn.name, genFunctionType(variant, nil, makeModuleContext(typeName, fn.name)))
  849. end
  850. end
  851. moduleDefs[#moduleDefs + 1] = 'end\n'
  852. end
  853. end
  854. end
  855. -- lovr module
  856. lovrDefs[#lovrDefs + 1] = 'global record lovr'
  857. for _, module in ipairs(modules) do
  858. if module.key == 'lovr' then
  859. for _, fn in ipairs(module.functions) do
  860. for _, variant in ipairs(fn.variants or {}) do
  861. lovrDefs[#lovrDefs + 1] = (' %s: %s'):format(fn.name, genFunctionType(variant, nil, extendContext('Lovr', fn.name)))
  862. end
  863. end
  864. end
  865. end
  866. for _, cb in ipairs(callbacks) do
  867. for _, variant in ipairs(cb.variants or {}) do
  868. lovrDefs[#lovrDefs + 1] = (' %s: %s'):format(cb.name, genFunctionType(variant, nil, extendContext('Lovr', cb.name)))
  869. end
  870. end
  871. for _, module in ipairs(modules) do
  872. if module.key ~= 'lovr' and not module.external then
  873. lovrDefs[#lovrDefs + 1] = (' %s: %s'):format(module.name, moduleInterfaces[module.key])
  874. end
  875. end
  876. lovrDefs[#lovrDefs + 1] = 'end\n'
  877. end
  878. local directory = lovr.filesystem.getSource() .. '/tl'
  879. mkdir(directory)
  880. local forwardDefs = {}
  881. for name in pairs(objectNames) do
  882. forwardDefs[#forwardDefs + 1] = 'global type ' .. name
  883. end
  884. for name in pairs(tableTypeForwards) do
  885. forwardDefs[#forwardDefs + 1] = 'global type ' .. name
  886. end
  887. local out = {}
  888. local function append(section)
  889. for _, line in ipairs(section) do
  890. out[#out + 1] = line
  891. end
  892. end
  893. append(preamble)
  894. if #forwardDefs > 0 then
  895. append(forwardDefs)
  896. out[#out + 1] = ''
  897. end
  898. append(enumDefs)
  899. append(objectDefs)
  900. if next(moduleNestedDefs) then
  901. local injected = {}
  902. for _, line in ipairs(moduleDefs) do
  903. injected[#injected + 1] = line
  904. local name = line:match('^global record%s+([%w_]+)$')
  905. local nested = name and moduleNestedDefs[name]
  906. if nested then
  907. for _, nestedLine in ipairs(nested) do
  908. injected[#injected + 1] = nestedLine
  909. end
  910. end
  911. end
  912. moduleDefs = injected
  913. end
  914. append(moduleDefs)
  915. if #lovrNestedDefs > 0 then
  916. local injected = {}
  917. local inserted = false
  918. for _, line in ipairs(lovrDefs) do
  919. injected[#injected + 1] = line
  920. if not inserted and line:match('^global record lovr') then
  921. for _, nested in ipairs(lovrNestedDefs) do
  922. injected[#injected + 1] = nested
  923. end
  924. inserted = true
  925. end
  926. end
  927. lovrDefs = injected
  928. end
  929. append(lovrDefs)
  930. if #tableTypeDefs > 0 then
  931. out[#out + 1] = ''
  932. append(tableTypeDefs)
  933. end
  934. writeFile(directory .. '/lovr.d.tl', table.concat(out, '\n'))
  935. end