-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathwau-scanner.lua
More file actions
370 lines (335 loc) · 11.2 KB
/
wau-scanner.lua
File metadata and controls
370 lines (335 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
local xml2lua = require("xml2lua")
local handler = require("xmlhandler.tree")
local function to_camel_case(s)
return s:gsub("_(.)", string.upper):gsub("^(.)", string.upper)
end
local function to_upper_case(s)
return s:upper()
end
-- parsing
local parser = {}
function parser.get_field(t, field)
-- the xml parser either sets
-- t["field"] = myfield; if there is only one occurence of "field" in t
-- t["field"] = { myfield1, myfield2, ... }; if there are multiple occurences
-- or t["field"] = nil; if there are none
-- In this function we are making sure that we get a list in any case
return t[field] and (#t[field] == 0 and {t[field]} or t[field]) or {}
end
function parser.description(handle)
if not handle then return nil end
local result = handle._attr or {}
result.long = handle[1]
return result
end
function parser.arg(handle)
if not handle._attr then error("Argument without attributes") end
local result = handle._attr
return result
end
function parser.request(handle)
local result = handle._attr or {}
result.description = parser.description(handle.description)
result.args = {}
local args = parser.get_field(handle, "arg")
for i, arg in ipairs(args) do
result.args[i] = parser.arg(arg)
end
return result
end
function parser.event(handle)
local result = handle._attr or {}
result.description = parser.description(handle.description)
result.args = {}
local args = parser.get_field(handle, "arg")
for i, arg in ipairs(args) do
result.args[i] = parser.arg(arg)
end
return result
end
function parser.enum(handle)
local result = handle._attr or {}
result.description = parser.description(handle.description)
result.entries = {}
local entries = parser.get_field(handle, "entry")
for i, entry in ipairs(entries) do
if not entry._attr then error("Enum entry without attributes") end
result.entries[i] = entry._attr
end
return result
end
function parser.interface(handle)
local result = handle._attr or {}
result.description = parser.description(handle.description)
result.requests = {}
local requests = parser.get_field(handle, "request")
for i, request in ipairs(requests) do
result.requests[i] = parser.request(request)
end
result.events = {}
local events = parser.get_field(handle, "event")
for i, event in ipairs(events) do
result.events[i] = parser.event(event)
end
result.enums = {}
local enums = parser.get_field(handle, "enum")
for i, enum in ipairs(enums) do
result.enums[i] = parser.enum(enum)
end
return result
end
function parser.protocol(handle)
local result = handle._attr or {}
result.copyright = handle.copyright
result.description = parser.description(handle.description)
result.interfaces = {}
local interfaces = parser.get_field(handle, "interface")
for i, iface in ipairs(interfaces) do
result.interfaces[i] = parser.interface(iface)
end
return result
end
-- printing
local function convert_type_to_signature(type_name, optional)
local res = optional and "?" or ""
if type_name == "int" then return res .. "i"
elseif type_name == "string" then return res .. "s"
elseif type_name == "fd" then return res .. "h"
elseif type_name == "fixed" then return res .. "f"
elseif type_name == "array" then return res .. "a"
elseif type_name == "object" then return res .. "o"
elseif type_name == "new_id" then return res .. "n"
elseif type_name == "uint" then return res .. "u"
else error("Couldn't resolve type", type_name) end
end
local function get_message_types(args)
local types = "{ "
for _, arg in ipairs(args) do
if arg.type == "object" or arg.type == "new_id" then
-- check if interface exists (what to do?)
local iface = arg.interface
and string.format("wau.%s", arg.interface)
or "0"
types = string.format("%s%s, ", types, iface)
else
types = types .. "0, "
end
end
return types .. "}"
end
local function get_message_signature(mes)
local signature = mes.since and mes.since or ""
for _, arg in ipairs(mes.args) do
signature = signature .. convert_type_to_signature(arg.type,
arg["allow-null"] and arg["allow-null"] == "true")
end
if signature == "un" then signature = "usun" end -- TODO
return signature
end
local printer = {
include_comments = true,
opcode_field = "_OpCode",
indent = 0,
indent_delta = 4,
}
function printer.indent_add()
printer.indent = printer.indent + printer.indent_delta
return printer
end
function printer.indent_sub()
printer.indent = printer.indent - printer.indent_delta
return printer
end
function printer.line(f, ...)
if not f then f = "" end
if f == "" then -- empty line without indent
io.stdout:write("\n")
return printer
end
local indent = ""
for _=1,printer.indent do
indent = string.format("%s ", indent)
end
f = string.format(f, ...)
f = f:gsub("\n%s*", " ") -- no new lines in one line printed
io.stdout:write("\n", indent, f)
return printer
end
function printer.comment(s)
if printer.include_comments then
local toprint = s:gsub("^%s*", "-- "):gsub("\n%s*", "\n-- ")
for line in (toprint ..'\n'):gmatch'(.-)\r?\n' do
printer.line(line)
end
end
return printer
end
function printer.message(mes)
printer.line([[{]])
printer.indent_add()
printer.line([[name = "%s",]], mes.name)
printer.line([[signature = "%s",]],
get_message_signature(mes))
printer.line([[types = %s,]],
get_message_types(mes.args))
if mes.type then
printer.line([[type = "%s"]], mes.type)
end
printer.indent_sub()
printer.line([[},]])
end
function printer.enum(enum)
printer.line([[["%s"] = {]], enum.name)
printer.indent_add()
for _, entry in ipairs(enum.entries) do
printer.line([[["%s"] = %s,]], entry.name, entry.value)
end
printer.indent_sub()
printer.line([[},]])
end
function printer.interface(iface)
printer.line([[--- %s]], iface.description.summary or iface.name)
if iface.description.long then
printer.line([[--]])
printer.comment(iface.description.long)
end
printer.line([[-- @type %s]], iface.name)
printer.line([[wau.%s:init {]], iface.name)
printer.indent_add()
-- basics
printer.line([[name = "%s",]], iface.name)
printer.line([[version = %s,]], iface.version)
-- methods
printer.line([[methods = {]])
printer.indent_add()
for _, request in ipairs(iface.requests) do
printer.line([[--- %s]], request.description.summary or request.name)
if request.description.long then
printer.line([[--]])
printer.comment(request.description.long)
end
printer.line([[-- @function %s:%s]], iface.name, request.name)
local returns = false
for _, arg in ipairs(request.args) do
if arg.type ~= "new_id" then
printer.line([[-- @tparam %s %s%s]], arg.type == "object" and arg.interface or arg.type, arg.name,
arg.summary and " " .. arg.summary or "")
else
returns = true
printer.line([[-- @treturn %s]], arg.interface or "object")
end
end
if not returns then
printer.line([[-- @treturn %s self]], iface.name)
end
printer.message(request)
end
printer.indent_sub()
printer.line([[},]])
-- events
printer.line([[events = {]])
printer.indent_add()
for _, event in ipairs(iface.events) do
printer.line([[--- %s]], event.description.summary or event.name)
if event.description.long then
printer.line([[--]])
printer.comment(event.description.long)
end
printer.line([[-- @event %s:%s]], iface.name, event.name)
for _, arg in ipairs(event.args) do
printer.line([[-- @tparam %s %s%s]], (arg.type == "object" or arg.type == "new_id")
and arg.interface or arg.type, arg.name,
arg.summary and " " .. arg.summary or "")
end
printer.message(event)
end
printer.indent_sub()
printer.line([[},]])
-- enums
printer.line([[enums = {]])
printer.indent_add()
for _, enum in ipairs(iface.enums) do
printer.line([[--- %s]], enum.description and enum.description.summary or enum.name)
if enum.description and enum.description.long then
printer.line([[--]])
printer.comment(enum.description.long)
end
printer.line([[-- @enum %s.%s]], iface.name, to_camel_case(enum.name))
for _, entry in ipairs(enum.entries) do
printer.line([[-- @param %s %s%s]],
to_upper_case(entry.name),
entry.value,
entry.summary and " " .. entry.summary or "")
end
printer.enum(enum)
end
printer.indent_sub()
printer.line([[},]])
-- method opcode
printer.line([[methods_opcode = {]])
printer.indent_add()
for i, request in ipairs(iface.requests) do
printer.line([[["%s"] = %s,]], request.name, i - 1)
end
printer.indent_sub()
printer.line([[},]])
printer.indent_sub()
printer.line([[}]])
printer.line()
end
function printer.protocol(protocol)
printer.line([[--- %s]], protocol.name)
printer.line([[-- @module %s]], protocol.name)
if protocol.copyright then
printer.line()
printer.comment(protocol.copyright)
end
printer.line([[return function(wau)]])
printer.line()
printer.line([[local interfaces = {]])
printer.indent_add()
for _, iface in ipairs(protocol.interfaces) do
printer.line([["%s",]], iface.name)
end
printer.indent_sub()
printer.line([[}]])
printer.line()
printer.line([[for _, iface in ipairs(interfaces) do]])
printer.indent_add()
printer.line([[wau[iface] = wau.wl_interface.new()]])
printer.indent_sub()
printer.line([[end]])
printer.line()
for _, iface in ipairs(protocol.interfaces) do
printer.interface(iface)
end
printer.line([[end]])
end
-- main
local function main(arg)
local content
for line in io.lines() do
content = content and string.format("%s\n%s", content, line) or line
end
assert(content)
-- check command line arguments
for i, a in ipairs(arg) do
if a == "-nc" or a == "--no-comment" then
printer.include_comments = false
elseif a == "-i" or a == "--indent" then
if arg[i + 1] and tonumber(arg[i + 1]) then
printer.indent_delta = tonumber(arg[i + 1])
end
end
end
local xml_parser = xml2lua.parser(handler)
xml_parser:parse(content)
assert(handler.root.protocol, "Failed to parse protocol")
local protocol = parser.protocol(handler.root.protocol)
if printer.include_comments then
io.stdout:write("-- Auto generated by the wau-scanner v0\n")
end
printer.protocol(protocol)
io.stdout:write("\n")
end
main(arg)