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, ...), 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, ...))
34 return string.gsub(str, "^[%s\n]*(.-)[%s\n]*$", "%1")
40 -- Some conversion tables {{{
58 for d, t in pairs(desc2ct) do
84 function add_chnd(fnt)
85 local odesc=string.gsub(fnt.odesc, "S", "s")
86 local idesc=string.gsub(fnt.idesc, "S", "s")
87 local str="l2chnd_" .. odesc .. "_" .. idesc .. "_"
89 for i, t in ipairs(fnt.itypes) do
93 chnds[str]={odesc=odesc, idesc=idesc, itypes=fnt.itypes}
97 function add_class(cls)
98 if cls~="Obj" and not classes[cls] then
103 function sort_classes(cls)
107 local function insert(cls)
108 if classes[cls] and not inserted[cls] then
109 if classes[cls].parent then
110 insert(classes[cls].parent)
113 table.insert(sorted, cls)
117 for cls in pairs(classes) do
124 function parse_type(t)
125 local desc, otype, varname="?", "", ""
127 -- Remove whitespace at start end end of the string and compress elsewhere.
128 t=string.gsub(trim(t), "[%s\n]+", " ")
129 -- Remove spaces around asterisks.
130 t=string.gsub(t, " *%* *", "*")
131 -- Add space after asterisks.
132 t=string.gsub(t, "%*", "* ")
136 local s, e=string.find(t, "^const +")
142 -- Find variable name part
144 s, e=string.find(tn, " ")
146 varname=string.sub(tn, e+1)
147 tn=string.sub(tn, 1, s-1)
148 assert(not string.find(varname, " "))
151 -- Try to check for supported types
152 desc = ct2desc[is_const .. tn]
154 if not desc or desc=="o" then
155 s, e=string.find(tn, "^[A-Z][%w_]*%*$")
158 otype=string.sub(tn, s, e-1)
161 errorf("Error parsing type from \"%s\"", t)
165 return desc, otype, varname
168 -- http://lua-users.org/wiki/SplitJoin
169 function gsplit2(s,sep)
170 local lasti, done, g = 1, false, s:gmatch('(.-)'..sep..'()')
172 if done then return end
174 if s == '' or sep == '' then done = true return s end
175 if v == nil then done = true return s:sub(lasti) end
186 -- Handle /*EXTL_DOC ... */
187 local function do_doc(s)
188 --s=string.gsub(s, "/%*EXTL_DOC(.-)%*/", "%1")
189 local st=string.len("/*EXTL_DOC")
190 local en, _=string.find(s, "%*/")
192 errorf("Could not find end of comment in \"%s...\"",
193 string.sub(s, 1, 50))
196 s=string.sub(s, st+1, en-1)
197 s=string.gsub(s, "\n[%s]*%*", "\n")
202 local function do_safe(s)
207 -- Handle EXTL_UNTRACED
208 local function do_untraced(s)
213 local function do_do_export(cls, efn, ot, fn, param)
214 local odesc, otype=parse_type(ot)
215 local idesc, itypes, ivars="", {}, {}
218 param=string.sub(param, 2, -2)
219 if string.find(param, "[()]") then
220 errorf("Error: parameters to %s contain parantheses", fn)
223 if string.len(param)>0 then
224 for p in gsplit2(param, ',') do
225 local spec, objtype, varname=parse_type(p)
227 table.insert(itypes, objtype)
228 table.insert(ivars, varname)
233 if string.sub(idesc, 1, 1)~="o" then
234 error("Invalid class for " .. fn)
239 -- Generate call handler name
257 if not classes[cls].fns then
261 assert(not classes[cls].fns[fn], "Function " .. fn .. " multiply defined!")
263 classes[cls].fns[fn]=fninfo
271 -- Handle EXTL_EXPORT otype fn(args)
272 local function do_export(s)
274 local pat="EXTL_EXPORT[%s\n]+([%w%s_*]+[%s\n*])([%w_]+)[%s\n]*(%b())"
275 local st, en, ot, fn, param=string.find(s, pat)
277 if not st then matcherr(s) end
279 if module=="global" or not module then
283 st, en, efn=string.find(fn, "^"..module.."_(.*)")
287 for k in pairs(reexports) do
288 st, en, efn=string.find(fn, "^"..k.."_(.*)")
297 error('"'..fn..'" is not a valid function name of format '..
298 'modulename_fnname.')
301 do_do_export(module, efn, ot, fn, param)
304 -- Handle EXTL_EXPORT_MEMBER otype prefix_fn(class, args)
305 local function do_export_member(s)
306 local pat="EXTL_EXPORT_MEMBER[%s\n]+([%w%s_*]+[%s\n*])([%w_]+)[%s\n]*(%b())"
307 local st, en, ot, fn, param=string.find(s, pat)
308 if not st then matcherr(s) end
309 local efn=string.gsub(fn, ".-_(.*)", "%1")
310 do_do_export("?", efn, ot, fn, param)
313 -- Handle EXTL_EXPORT_AS(table, member_fn) otype fn(args)
314 local function do_export_as(s)
315 local pat="EXTL_EXPORT_AS%(%s*([%w_]+)%s*,%s*([%w_]+)%s*%)[%s\n]+([%w%s_*]+[%s\n*])([%w_]+)[%s\n]*(%b())"
316 local st, en, cls, efn, ot, fn, param=string.find(s, pat)
317 if not st then matcherr(s) end
318 do_do_export((reexports[cls] and module or cls), efn, ot, fn, param)
321 local function do_implobj(s)
322 local pat="IMPLCLASS%(%s*([%w_]+)%s*,%s*([%w_]+)%s*,[^)]*%)"
323 local st, en, cls, par=string.find(s, pat)
324 if not st then matcherr(s) end
326 classes[cls].parent=par
329 local function do_class(s)
330 local pat="EXTL_CLASS%(%s*([%w_]+)%s*,%s*([%w_]+)%s*%)"
331 local st, en, cls, par=string.find(s, pat)
332 if not st then matcherr(s) end
334 classes[cls].parent=par
338 {"/%*EXTL_DOC", do_doc},
339 {"[%s\n]EXTL_SAFE[%s\n]", do_safe},
340 {"[%s\n]EXTL_UNTRACED[%s\n]", do_untraced},
341 {"[%s\n]EXTL_EXPORT[%s\n]+IMPLCLASS", do_implobj},
342 {"[%s\n]EXTL_EXPORT[%s\n]", do_export},
343 {"[%s\n]EXTL_EXPORT_AS", do_export_as},
344 {"[%s\n]EXTL_EXPORT_MEMBER[%s\n]", do_export_member},
345 {"[%s\n]EXTL_CLASS", do_class},
351 function do_parse(d, lookfor)
353 local mins, mine, minfn=string.len(d)+1, nil, nil
354 for _, lf in ipairs(lookfor) do
355 local s, e=string.find(d, lf[1])
357 mins, mine, minfn=s, e, lf[2]
365 minfn(string.sub(d, mins))
366 d=string.sub(d, mine)
373 -- Parser for Lua code documentation {{{
375 function parse_luadoc(d)
376 function do_luadoc(s_)
377 local st, en, b, s=string.find(s_, "\n%-%-DOC(.-)(\n.*)")
378 if string.find(b, "[^%s]") then
379 errorf("Syntax error while parsing \"--DOC%s\"", b)
383 st, en, docl=string.find(s, "^\n%s*%-%-([^\n]*\n)")
394 st, en, fn, param=string.find(s, "^\n[%s\n]*function%s*([%w_:%.]+)%s*(%b())")
397 errorf("Syntax error while parsing \"%s\"",
398 string.sub(s, 1, 50))
401 st, en, cls, clsfn=string.find(fn, "^([^.]*)%.(.*)$")
403 if cls and clsfn then
416 if not classes[cls].fns then
419 classes[cls].fns[fn]=fninfo
422 do_parse(d, {{"\n%-%-DOC", do_luadoc}})
430 function writechnd(h, name, info)
431 local oct=desc2ct[info.odesc]
435 static bool %s(%s (*fn)(), ExtlL2Param *in, ExtlL2Param *out)
440 -- Generate type checking code
441 for k, t in pairs(info.itypes) do
444 fprintf(h, " if(!EXTL_CHKO1(in, %d, %s)) return FALSE;\n",
447 fprintf(h, " if(!EXTL_CHKO(in, %d, %s)) return FALSE;\n",
453 -- Generate function call code
454 if info.odesc=="v" then
457 fprintf(h, " out[0].%s=fn(", info.odesc)
461 for k=1, string.len(info.idesc) do
462 fprintf(h, comma .. "in[%d].%s", k-1, string.sub(info.idesc, k, k))
465 fprintf(h, ");\n return TRUE;\n}\n")
468 function bool2str4c(b)
469 return (b and "TRUE" or "FALSE")
472 function write_class_fns(h, cls, data)
473 fprintf(h, "\n\nstatic ExtlExportedFnSpec %s_exports[] = {\n", cls)
475 for fn, info in pairs(data.fns) do
476 local ods, ids="NULL", "NULL"
477 if info.odesc~="v" then
478 ods='"' .. info.odesc .. '"'
481 if info.idesc~="" then
482 ids='"' .. info.idesc .. '"'
485 fprintf(h, " {\"%s\", %s, %s, %s, (ExtlL2CallHandler*)%s, %s, %s, FALSE},\n",
486 info.exported_name, fn, ids, ods, info.chnd,
487 bool2str4c(info.safe),
488 bool2str4c(info.untraced))
491 fprintf(h, " {NULL, NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE}\n};\n\n")
495 local function pfx(modname)
496 if modname=="global" or not modname then
504 function write_exports(h)
508 /* Automatically generated by mkexports.lua */
509 #include <libextl/extl.h>
510 #include <libextl/private.h>
515 -- Write class infos and check that the class is implemented in the
517 for c, data in pairs(classes) do
518 if string.lower(c)==c then
521 fprintf(h, "EXTL_DEFCLASS(%s);\n", c)
522 if data.fns and not data.parent then
523 error(c..": Methods can only be registered if the class "
524 .. "is implemented in the module in question.")
529 -- Write L2 call handlers
530 for name, info in pairs(chnds) do
531 writechnd(h, name, info)
536 for cls, data in pairs(classes) do
538 -- Write function declarations
539 for fn in pairs(data.fns) do
540 fprintf(h, "extern void %s();\n", fn)
542 -- Write function table
543 write_class_fns(h, cls, data)
545 fprintf(h, "#define %s_exports NULL\n", cls)
549 fprintf(h, "bool %sregister_exports()\n{\n", pfx(module))
551 local sorted_classes=sort_classes()
553 for _, cls in pairs(sorted_classes) do
554 if cls=="global" then
555 fprintf(h, " if(!extl_register_functions(global_exports)) return FALSE;\n")
556 elseif classes[cls].module then
557 fprintf(h, " if(!extl_register_module(\"%s\", %s_exports)) return FALSE;\n",
559 elseif classes[cls].parent then
560 fprintf(h, " if(!extl_register_class(\"%s\", %s_exports, \"%s\")) return FALSE;\n",
561 cls, cls, classes[cls].parent)
565 fprintf(h, " return TRUE;\n}\n\nvoid %sunregister_exports()\n{\n",
568 for _, cls in pairs(sorted_classes) do
569 if cls=="global" then
570 fprintf(h, " extl_unregister_functions(global_exports);\n")
571 elseif classes[cls].module then
572 fprintf(h, " extl_unregister_module(\"%s\", %s_exports);\n",
574 elseif classes[cls].parent then
575 fprintf(h, " extl_unregister_class(\"%s\", %s_exports);\n",
584 function write_header(h)
586 local u=string.upper(p)
588 /* Automatically generated by mkexports.lua */
589 #ifndef %sEXTL_EXPORTS_H
590 #define %sEXTL_EXPORTS_H
592 #include <libextl/extl.h>
594 extern bool %sregister_exports();
595 extern void %sunregister_exports();
597 #endif /* %sEXTL_EXPORTS_H */
605 -- Documentation output {{{
607 function tohuman(desc, objtype)
611 return desc2human[desc]
615 function texfriendly(name)
616 return string.gsub(name, "_", "-")
619 function texfriendly_typeormod(nm)
620 if string.find(nm, "A-Z") then
621 return "\\type{"..string.gsub(nm, '_', '\\_').."}"
623 return "\\code{"..nm.."}"
627 function write_fndoc(h, fn, info)
631 fprintf(h, "\\begin{function}\n")
632 if info.exported_name then
633 fn=info.exported_name
637 if info.class~="global" then
638 fprintf(h, "\\index{%s@%s!", texfriendly(info.class),
639 texfriendly_typeormod(info.class));
640 fprintf(h, "%s@\\code{%s}}\n", texfriendly(fn), fn)
642 fprintf(h, "\\index{%s@\\code{%s}}\n", texfriendly(fn), fn)
645 if info.class~="global" then
646 fprintf(h, "\\hyperlabel{fn:%s.%s}", info.class, fn)
648 fprintf(h, "\\hyperlabel{fn:%s}", fn)
651 fprintf(h, "\\synopsis{")
653 h:write(tohuman(info.odesc, info.otype).." ")
656 if info.class~="global" then
657 fprintf(h, "%s.", info.class)
660 if not info.ivars then
662 fprintf(h, "%s%s}", fn, info.paramstr)
664 fprintf(h, "%s(", fn)
666 for i, varname in pairs(info.ivars) do
667 fprintf(h, comma .. "%s", tohuman(string.sub(info.idesc, i, i),
670 fprintf(h, " %s", varname)
676 h:write("\\begin{funcdesc}\n" .. trim(info.doc) ..
677 (info.safe and "\n This function is considered safe." or "") ..
678 "\n\\end{funcdesc}\n")
679 fprintf(h, "\\end{function}\n\n")
683 function write_class_documentation(h, cls, in_subsect)
686 if not classes[cls] or not classes[cls].fns then
691 fprintf(h, "\n\n\\subsection{\\type{%s} functions}\n\n", cls)
694 for fn in pairs(classes[cls].fns) do
695 table.insert(sorted, fn)
699 for _, fn in ipairs(sorted) do
700 write_fndoc(h, fn, classes[cls].fns[fn])
705 function write_documentation(h)
708 write_class_documentation(h, module, false)
710 for cls in pairs(classes) do
712 table.insert(sorted, cls)
717 for _, cls in ipairs(sorted) do
718 write_class_documentation(h, cls, true)
737 Usage: libextl-mkexports [options] files...
739 Where options include:
751 if arg[i]=="-help" then
753 elseif arg[i]=="-mkdoc" then
755 elseif arg[i]=="-o" then
758 elseif arg[i]=="-h" then
761 elseif arg[i]=="-module" then
765 error("No module given")
767 elseif arg[i]=="-reexport" then
769 reexports[arg[i]]=true
771 table.insert(inputs, arg[i])
780 for _, ifnam in pairs(inputs) do
781 h, err=io.open(ifnam, "r")
783 errorf("Could not open %s: %s", ifnam, err)
785 print("Scanning " .. ifnam .. " for exports.")
788 if string.find(ifnam, "%.lua$") then
790 parse_luadoc("\n" .. data .. "\n")
791 elseif string.find(ifnam, "%.c$") then
792 parse("\n" .. data .. "\n")
794 error('Unknown file')
800 outh, err=io.open(output_file, "w")
807 write_documentation(outh)
811 local hh, err=io.open(header_file, "w")