5 -- Copyright (c) Tuomo Valkonen 2003-2005.
7 -- Ion is free software; you can redistribute it and/or modify it under
8 -- the terms of the GNU Lesser General Public License as published by
9 -- the Free Software Foundation; either version 2.1 of the License, or
10 -- (at your option) any later version.
13 -- This is a script to automatically generate exported function registration
14 -- code and documentation for those from C source.
16 -- The script can also parse documentation comments from Lua code.
19 -- Helper functions {{{
21 function errorf(fmt, ...)
22 error(string.format(fmt, unpack(arg)), 2)
26 error(string.format("Parse error in \"%s...\"", string.sub(s, 1, 50)), 2)
29 function fprintf(h, fmt, ...)
30 h:write(string.format(fmt, unpack(arg)))
34 return string.gsub(str, "^[%s\n]*(.-)[%s\n]*$", "%1")
40 -- Some conversion tables {{{
58 for d, t in pairs(desc2ct) do
83 function add_chnd(fnt)
84 local odesc=string.gsub(fnt.odesc, "S", "s")
85 local idesc=string.gsub(fnt.idesc, "S", "s")
86 local str="l2chnd_" .. odesc .. "_" .. idesc .. "_"
88 for i, t in ipairs(fnt.itypes) do
92 chnds[str]={odesc=odesc, idesc=idesc, itypes=fnt.itypes}
96 function add_class(cls)
97 if cls~="Obj" and not classes[cls] then
102 function sort_classes(cls)
106 local function insert(cls)
107 if classes[cls] and not inserted[cls] then
108 if classes[cls].parent then
109 insert(classes[cls].parent)
112 table.insert(sorted, cls)
116 for cls in pairs(classes) do
123 function parse_type(t)
124 local desc, otype, varname="?", "", ""
126 -- Remove whitespace at start end end of the string and compress elsewhere.
127 t=string.gsub(trim(t), "[%s\n]+", " ")
128 -- Remove spaces around asterisks.
129 t=string.gsub(t, " *%* *", "*")
130 -- Add space after asterisks.
131 t=string.gsub(t, "%*", "* ")
135 local s, e=string.find(t, "^const +")
141 -- Find variable name part
143 s, e=string.find(tn, " ")
145 varname=string.sub(tn, e+1)
146 tn=string.sub(tn, 1, s-1)
147 assert(not string.find(varname, " "))
150 -- Try to check for supported types
151 desc = ct2desc[is_const .. tn]
153 if not desc or desc=="o" then
154 s, e=string.find(tn, "^[A-Z][%w_]*%*$")
157 otype=string.sub(tn, s, e-1)
160 errorf("Error parsing type from \"%s\"", t)
164 return desc, otype, varname
172 -- Handle /*EXTL_DOC ... */
173 local function do_doc(s)
174 --s=string.gsub(s, "/%*EXTL_DOC(.-)%*/", "%1")
175 local st=string.len("/*EXTL_DOC")
176 local en, _=string.find(s, "%*/")
178 errorf("Could not find end of comment in \"%s...\"",
179 string.sub(s, 1, 50))
182 s=string.sub(s, st+1, en-1)
183 s=string.gsub(s, "\n[%s]*%*", "\n")
188 local function do_safe(s)
193 -- Handle EXTL_UNTRACED
194 local function do_untraced(s)
199 local function do_do_export(cls, efn, ot, fn, param)
200 local odesc, otype=parse_type(ot)
201 local idesc, itypes, ivars="", {}, {}
204 param=string.sub(param, 2, -2)
205 if string.find(param, "[()]") then
206 errorf("Error: parameters to %s contain parantheses", fn)
209 if string.len(param)>0 then
210 for p in string.gfind(param .. ",", "([^,]*),") do
211 local spec, objtype, varname=parse_type(p)
213 table.insert(itypes, objtype)
214 table.insert(ivars, varname)
219 if string.sub(idesc, 1, 1)~="o" then
220 error("Invalid class for " .. fn)
225 -- Generate call handler name
243 if not classes[cls].fns then
247 assert(not classes[cls].fns[fn], "Function " .. fn .. " multiply defined!")
249 classes[cls].fns[fn]=fninfo
257 -- Handle EXTL_EXPORT otype fn(args)
258 local function do_export(s)
260 local pat="EXTL_EXPORT[%s\n]+([%w%s_*]+[%s\n*])([%w_]+)[%s\n]*(%b())"
261 local st, en, ot, fn, param=string.find(s, pat)
263 if not st then matcherr(s) end
265 if module=="global" or not module then
269 st, en, efn=string.find(fn, "^"..module.."_(.*)")
273 for k in pairs(reexports) do
274 st, en, efn=string.find(fn, "^"..k.."_(.*)")
283 error('"'..fn..'" is not a valid function name of format '..
284 'modulename_fnname.')
287 do_do_export(module, efn, ot, fn, param)
290 -- Handle EXTL_EXPORT_MEMBER otype prefix_fn(class, args)
291 local function do_export_member(s)
292 local pat="EXTL_EXPORT_MEMBER[%s\n]+([%w%s_*]+[%s\n*])([%w_]+)[%s\n]*(%b())"
293 local st, en, ot, fn, param=string.find(s, pat)
294 if not st then matcherr(s) end
295 local efn=string.gsub(fn, ".-_(.*)", "%1")
296 do_do_export("?", efn, ot, fn, param)
299 -- Handle EXTL_EXPORT_AS(table, member_fn) otype fn(args)
300 local function do_export_as(s)
301 local pat="EXTL_EXPORT_AS%(%s*([%w_]+)%s*,%s*([%w_]+)%s*%)[%s\n]+([%w%s_*]+[%s\n*])([%w_]+)[%s\n]*(%b())"
302 local st, en, cls, efn, ot, fn, param=string.find(s, pat)
303 if not st then matcherr(s) end
304 do_do_export((reexports[cls] and module or cls), efn, ot, fn, param)
307 local function do_implobj(s)
308 local pat="IMPLCLASS%(%s*([%w_]+)%s*,%s*([%w_]+)%s*,[^)]*%)"
309 local st, en, cls, par=string.find(s, pat)
310 if not st then matcherr(s) end
312 classes[cls].parent=par
315 local function do_class(s)
316 local pat="EXTL_CLASS%(%s*([%w_]+)%s*,%s*([%w_]+)%s*%)"
317 local st, en, cls, par=string.find(s, pat)
318 if not st then matcherr(s) end
320 classes[cls].parent=par
324 {"/%*EXTL_DOC", do_doc},
325 {"[%s\n]EXTL_SAFE[%s\n]", do_safe},
326 {"[%s\n]EXTL_UNTRACED[%s\n]", do_untraced},
327 {"[%s\n]EXTL_EXPORT[%s\n]+IMPLCLASS", do_implobj},
328 {"[%s\n]EXTL_EXPORT[%s\n]", do_export},
329 {"[%s\n]EXTL_EXPORT_AS", do_export_as},
330 {"[%s\n]EXTL_EXPORT_MEMBER[%s\n]", do_export_member},
331 {"[%s\n]EXTL_CLASS", do_class},
337 function do_parse(d, lookfor)
339 local mins, mine, minfn=string.len(d)+1, nil, nil
340 for _, lf in ipairs(lookfor) do
341 local s, e=string.find(d, lf[1])
343 mins, mine, minfn=s, e, lf[2]
351 minfn(string.sub(d, mins))
352 d=string.sub(d, mine)
359 -- Parser for Lua code documentation {{{
361 function parse_luadoc(d)
362 function do_luadoc(s_)
363 local st, en, b, s=string.find(s_, "\n%-%-DOC(.-)(\n.*)")
364 if string.find(b, "[^%s]") then
365 errorf("Syntax error while parsing \"--DOC%s\"", b)
369 st, en, docl=string.find(s, "^\n%s*%-%-([^\n]*\n)")
380 st, en, fn, param=string.find(s, "^\n[%s\n]*function%s*([%w_:%.]+)%s*(%b())")
383 errorf("Syntax error while parsing \"%s\"",
384 string.sub(s, 1, 50))
387 st, en, cls, clsfn=string.find(fn, "^([^.]*)%.(.*)$")
389 if cls and clsfn then
402 if not classes[cls].fns then
405 classes[cls].fns[fn]=fninfo
408 do_parse(d, {{"\n%-%-DOC", do_luadoc}})
416 function writechnd(h, name, info)
417 local oct=desc2ct[info.odesc]
421 static bool %s(%s (*fn)(), ExtlL2Param *in, ExtlL2Param *out)
426 -- Generate type checking code
427 for k, t in pairs(info.itypes) do
430 fprintf(h, " if(!EXTL_CHKO1(in, %d, %s)) return FALSE;\n",
433 fprintf(h, " if(!EXTL_CHKO(in, %d, %s)) return FALSE;\n",
439 -- Generate function call code
440 if info.odesc=="v" then
443 fprintf(h, " out[0].%s=fn(", info.odesc)
447 for k=1, string.len(info.idesc) do
448 fprintf(h, comma .. "in[%d].%s", k-1, string.sub(info.idesc, k, k))
451 fprintf(h, ");\n return TRUE;\n}\n")
454 function bool2str4c(b)
455 return (b and "TRUE" or "FALSE")
458 function write_class_fns(h, cls, data)
459 fprintf(h, "\n\nstatic ExtlExportedFnSpec %s_exports[] = {\n", cls)
461 for fn, info in pairs(data.fns) do
462 local ods, ids="NULL", "NULL"
463 if info.odesc~="v" then
464 ods='"' .. info.odesc .. '"'
467 if info.idesc~="" then
468 ids='"' .. info.idesc .. '"'
471 fprintf(h, " {\"%s\", %s, %s, %s, (ExtlL2CallHandler*)%s, %s, %s},\n",
472 info.exported_name, fn, ids, ods, info.chnd,
473 bool2str4c(info.safe),
474 bool2str4c(info.untraced))
477 fprintf(h, " {NULL, NULL, NULL, NULL, NULL, FALSE, FALSE}\n};\n\n")
481 local function pfx(modname)
482 if modname=="global" or not modname then
490 function write_exports(h)
494 /* Automatically generated by mkexports.lua */
495 #include <libextl/extl.h>
496 #include <libextl/private.h>
501 -- Write class infos and check that the class is implemented in the
503 for c, data in pairs(classes) do
504 if string.lower(c)==c then
507 fprintf(h, "EXTL_DEFCLASS(%s);\n", c)
508 if data.fns and not data.parent then
509 error(c..": Methods can only be registered if the class "
510 .. "is implemented in the module in question.")
515 -- Write L2 call handlers
516 for name, info in pairs(chnds) do
517 writechnd(h, name, info)
522 for cls, data in pairs(classes) do
524 -- Write function declarations
525 for fn in pairs(data.fns) do
526 fprintf(h, "extern void %s();\n", fn)
528 -- Write function table
529 write_class_fns(h, cls, data)
531 fprintf(h, "#define %s_exports NULL\n", cls)
535 fprintf(h, "bool %sregister_exports()\n{\n", pfx(module))
537 local sorted_classes=sort_classes()
539 for _, cls in pairs(sorted_classes) do
540 if cls=="global" then
541 fprintf(h, " if(!extl_register_functions(global_exports)) return FALSE;\n")
542 elseif classes[cls].module then
543 fprintf(h, " if(!extl_register_module(\"%s\", %s_exports)) return FALSE;\n",
545 elseif classes[cls].parent then
546 fprintf(h, " if(!extl_register_class(\"%s\", %s_exports, \"%s\")) return FALSE;\n",
547 cls, cls, classes[cls].parent)
551 fprintf(h, " return TRUE;\n}\n\nvoid %sunregister_exports()\n{\n",
554 for _, cls in pairs(sorted_classes) do
555 if cls=="global" then
556 fprintf(h, " extl_unregister_functions(global_exports);\n")
557 elseif classes[cls].module then
558 fprintf(h, " extl_unregister_module(\"%s\", %s_exports);\n",
560 elseif classes[cls].parent then
561 fprintf(h, " extl_unregister_class(\"%s\", %s_exports);\n",
570 function write_header(h)
572 local u=string.upper(p)
574 /* Automatically generated by mkexports.lua */
575 #ifndef %sEXTL_EXPORTS_H
576 #define %sEXTL_EXPORTS_H
578 #include <libextl/extl.h>
580 extern bool %sregister_exports();
581 extern void %sunregister_exports();
583 #endif /* %sEXTL_EXPORTS_H */
591 -- Documentation output {{{
593 function tohuman(desc, objtype)
597 return desc2human[desc]
601 function texfriendly(name)
602 return string.gsub(name, "_", "-")
605 function texfriendly_typeormod(nm)
606 if string.find(nm, "A-Z") then
607 return "\\type{"..string.gsub(nm, '_', '\_').."}"
609 return "\\code{"..nm.."}"
613 function write_fndoc(h, fn, info)
617 fprintf(h, "\\begin{function}\n")
618 if info.exported_name then
619 fn=info.exported_name
622 if info.class~="global" then
623 fprintf(h, "\\index{%s@%s!", texfriendly(info.class),
624 texfriendly_typeormod(info.class));
625 fprintf(h, "%s@\\code{%s}}\n", texfriendly(fn), fn)
627 fprintf(h, "\\index{%s@\\code{%s}}\n", texfriendly(fn), fn)
629 if info.class~="global" then
630 fprintf(h, "\\hyperlabel{fn:%s.%s}", info.class, fn)
632 fprintf(h, "\\hyperlabel{fn:%s}", fn)
635 fprintf(h, "\\synopsis{")
637 h:write(tohuman(info.odesc, info.otype).." ")
640 if info.class~="global" then
641 fprintf(h, "%s.", info.class)
644 if not info.ivars then
646 fprintf(h, "%s%s}", fn, info.paramstr)
648 fprintf(h, "%s(", fn)
650 for i, varname in pairs(info.ivars) do
651 fprintf(h, comma .. "%s", tohuman(string.sub(info.idesc, i, i),
654 fprintf(h, " %s", varname)
660 h:write("\\begin{funcdesc}\n" .. trim(info.doc).. "\n\\end{funcdesc}\n")
661 fprintf(h, "\\end{function}\n\n")
665 function write_class_documentation(h, cls, in_subsect)
668 if not classes[cls] or not classes[cls].fns then
673 fprintf(h, "\n\n\\subsection{\\type{%s} functions}\n\n", cls)
676 for fn in pairs(classes[cls].fns) do
677 table.insert(sorted, fn)
681 for _, fn in ipairs(sorted) do
682 write_fndoc(h, fn, classes[cls].fns[fn])
687 function write_documentation(h)
690 write_class_documentation(h, module, false)
692 for cls in pairs(classes) do
694 table.insert(sorted, cls)
699 for _, cls in ipairs(sorted) do
700 write_class_documentation(h, cls, true)
719 Usage: libextl-mkexports [options] files...
721 Where options include:
733 if arg[i]=="-help" then
735 elseif arg[i]=="-mkdoc" then
737 elseif arg[i]=="-o" then
740 elseif arg[i]=="-h" then
743 elseif arg[i]=="-module" then
747 error("No module given")
749 elseif arg[i]=="-reexport" then
751 reexports[arg[i]]=true
753 table.insert(inputs, arg[i])
758 if table.getn(inputs)==0 then
762 for _, ifnam in pairs(inputs) do
763 h, err=io.open(ifnam, "r")
765 errorf("Could not open %s: %s", ifnam, err)
767 print("Scanning " .. ifnam .. " for exports.")
770 if string.find(ifnam, "%.lua$") then
772 parse_luadoc("\n" .. data .. "\n")
773 elseif string.find(ifnam, "%.c$") then
774 parse("\n" .. data .. "\n")
776 error('Unknown file')
782 outh, err=io.open(output_file, "w")
789 write_documentation(outh)
793 local hh, err=io.open(header_file, "w")