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
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
173 -- Handle /*EXTL_DOC ... */
174 local function do_doc(s)
175 --s=string.gsub(s, "/%*EXTL_DOC(.-)%*/", "%1")
176 local st=string.len("/*EXTL_DOC")
177 local en, _=string.find(s, "%*/")
179 errorf("Could not find end of comment in \"%s...\"",
180 string.sub(s, 1, 50))
183 s=string.sub(s, st+1, en-1)
184 s=string.gsub(s, "\n[%s]*%*", "\n")
189 local function do_safe(s)
194 -- Handle EXTL_UNTRACED
195 local function do_untraced(s)
200 local function do_do_export(cls, efn, ot, fn, param)
201 local odesc, otype=parse_type(ot)
202 local idesc, itypes, ivars="", {}, {}
205 param=string.sub(param, 2, -2)
206 if string.find(param, "[()]") then
207 errorf("Error: parameters to %s contain parantheses", fn)
210 if string.len(param)>0 then
211 for p in string.gfind(param .. ",", "([^,]*),") do
212 local spec, objtype, varname=parse_type(p)
214 table.insert(itypes, objtype)
215 table.insert(ivars, varname)
220 if string.sub(idesc, 1, 1)~="o" then
221 error("Invalid class for " .. fn)
226 -- Generate call handler name
244 if not classes[cls].fns then
248 assert(not classes[cls].fns[fn], "Function " .. fn .. " multiply defined!")
250 classes[cls].fns[fn]=fninfo
258 -- Handle EXTL_EXPORT otype fn(args)
259 local function do_export(s)
261 local pat="EXTL_EXPORT[%s\n]+([%w%s_*]+[%s\n*])([%w_]+)[%s\n]*(%b())"
262 local st, en, ot, fn, param=string.find(s, pat)
264 if not st then matcherr(s) end
266 if module=="global" or not module then
270 st, en, efn=string.find(fn, "^"..module.."_(.*)")
274 for k in pairs(reexports) do
275 st, en, efn=string.find(fn, "^"..k.."_(.*)")
284 error('"'..fn..'" is not a valid function name of format '..
285 'modulename_fnname.')
288 do_do_export(module, efn, ot, fn, param)
291 -- Handle EXTL_EXPORT_MEMBER otype prefix_fn(class, args)
292 local function do_export_member(s)
293 local pat="EXTL_EXPORT_MEMBER[%s\n]+([%w%s_*]+[%s\n*])([%w_]+)[%s\n]*(%b())"
294 local st, en, ot, fn, param=string.find(s, pat)
295 if not st then matcherr(s) end
296 local efn=string.gsub(fn, ".-_(.*)", "%1")
297 do_do_export("?", efn, ot, fn, param)
300 -- Handle EXTL_EXPORT_AS(table, member_fn) otype fn(args)
301 local function do_export_as(s)
302 local pat="EXTL_EXPORT_AS%(%s*([%w_]+)%s*,%s*([%w_]+)%s*%)[%s\n]+([%w%s_*]+[%s\n*])([%w_]+)[%s\n]*(%b())"
303 local st, en, cls, efn, ot, fn, param=string.find(s, pat)
304 if not st then matcherr(s) end
305 do_do_export((reexports[cls] and module or cls), efn, ot, fn, param)
308 local function do_implobj(s)
309 local pat="IMPLCLASS%(%s*([%w_]+)%s*,%s*([%w_]+)%s*,[^)]*%)"
310 local st, en, cls, par=string.find(s, pat)
311 if not st then matcherr(s) end
313 classes[cls].parent=par
316 local function do_class(s)
317 local pat="EXTL_CLASS%(%s*([%w_]+)%s*,%s*([%w_]+)%s*%)"
318 local st, en, cls, par=string.find(s, pat)
319 if not st then matcherr(s) end
321 classes[cls].parent=par
325 {"/%*EXTL_DOC", do_doc},
326 {"[%s\n]EXTL_SAFE[%s\n]", do_safe},
327 {"[%s\n]EXTL_UNTRACED[%s\n]", do_untraced},
328 {"[%s\n]EXTL_EXPORT[%s\n]+IMPLCLASS", do_implobj},
329 {"[%s\n]EXTL_EXPORT[%s\n]", do_export},
330 {"[%s\n]EXTL_EXPORT_AS", do_export_as},
331 {"[%s\n]EXTL_EXPORT_MEMBER[%s\n]", do_export_member},
332 {"[%s\n]EXTL_CLASS", do_class},
338 function do_parse(d, lookfor)
340 local mins, mine, minfn=string.len(d)+1, nil, nil
341 for _, lf in ipairs(lookfor) do
342 local s, e=string.find(d, lf[1])
344 mins, mine, minfn=s, e, lf[2]
352 minfn(string.sub(d, mins))
353 d=string.sub(d, mine)
360 -- Parser for Lua code documentation {{{
362 function parse_luadoc(d)
363 function do_luadoc(s_)
364 local st, en, b, s=string.find(s_, "\n%-%-DOC(.-)(\n.*)")
365 if string.find(b, "[^%s]") then
366 errorf("Syntax error while parsing \"--DOC%s\"", b)
370 st, en, docl=string.find(s, "^\n%s*%-%-([^\n]*\n)")
381 st, en, fn, param=string.find(s, "^\n[%s\n]*function%s*([%w_:%.]+)%s*(%b())")
384 errorf("Syntax error while parsing \"%s\"",
385 string.sub(s, 1, 50))
388 st, en, cls, clsfn=string.find(fn, "^([^.]*)%.(.*)$")
390 if cls and clsfn then
403 if not classes[cls].fns then
406 classes[cls].fns[fn]=fninfo
409 do_parse(d, {{"\n%-%-DOC", do_luadoc}})
417 function writechnd(h, name, info)
418 local oct=desc2ct[info.odesc]
422 static bool %s(%s (*fn)(), ExtlL2Param *in, ExtlL2Param *out)
427 -- Generate type checking code
428 for k, t in pairs(info.itypes) do
431 fprintf(h, " if(!EXTL_CHKO1(in, %d, %s)) return FALSE;\n",
434 fprintf(h, " if(!EXTL_CHKO(in, %d, %s)) return FALSE;\n",
440 -- Generate function call code
441 if info.odesc=="v" then
444 fprintf(h, " out[0].%s=fn(", info.odesc)
448 for k=1, string.len(info.idesc) do
449 fprintf(h, comma .. "in[%d].%s", k-1, string.sub(info.idesc, k, k))
452 fprintf(h, ");\n return TRUE;\n}\n")
455 function bool2str4c(b)
456 return (b and "TRUE" or "FALSE")
459 function write_class_fns(h, cls, data)
460 fprintf(h, "\n\nstatic ExtlExportedFnSpec %s_exports[] = {\n", cls)
462 for fn, info in pairs(data.fns) do
463 local ods, ids="NULL", "NULL"
464 if info.odesc~="v" then
465 ods='"' .. info.odesc .. '"'
468 if info.idesc~="" then
469 ids='"' .. info.idesc .. '"'
472 fprintf(h, " {\"%s\", %s, %s, %s, (ExtlL2CallHandler*)%s, %s, %s, FALSE},\n",
473 info.exported_name, fn, ids, ods, info.chnd,
474 bool2str4c(info.safe),
475 bool2str4c(info.untraced))
478 fprintf(h, " {NULL, NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE}\n};\n\n")
482 local function pfx(modname)
483 if modname=="global" or not modname then
491 function write_exports(h)
495 /* Automatically generated by mkexports.lua */
496 #include <libextl/extl.h>
497 #include <libextl/private.h>
502 -- Write class infos and check that the class is implemented in the
504 for c, data in pairs(classes) do
505 if string.lower(c)==c then
508 fprintf(h, "EXTL_DEFCLASS(%s);\n", c)
509 if data.fns and not data.parent then
510 error(c..": Methods can only be registered if the class "
511 .. "is implemented in the module in question.")
516 -- Write L2 call handlers
517 for name, info in pairs(chnds) do
518 writechnd(h, name, info)
523 for cls, data in pairs(classes) do
525 -- Write function declarations
526 for fn in pairs(data.fns) do
527 fprintf(h, "extern void %s();\n", fn)
529 -- Write function table
530 write_class_fns(h, cls, data)
532 fprintf(h, "#define %s_exports NULL\n", cls)
536 fprintf(h, "bool %sregister_exports()\n{\n", pfx(module))
538 local sorted_classes=sort_classes()
540 for _, cls in pairs(sorted_classes) do
541 if cls=="global" then
542 fprintf(h, " if(!extl_register_functions(global_exports)) return FALSE;\n")
543 elseif classes[cls].module then
544 fprintf(h, " if(!extl_register_module(\"%s\", %s_exports)) return FALSE;\n",
546 elseif classes[cls].parent then
547 fprintf(h, " if(!extl_register_class(\"%s\", %s_exports, \"%s\")) return FALSE;\n",
548 cls, cls, classes[cls].parent)
552 fprintf(h, " return TRUE;\n}\n\nvoid %sunregister_exports()\n{\n",
555 for _, cls in pairs(sorted_classes) do
556 if cls=="global" then
557 fprintf(h, " extl_unregister_functions(global_exports);\n")
558 elseif classes[cls].module then
559 fprintf(h, " extl_unregister_module(\"%s\", %s_exports);\n",
561 elseif classes[cls].parent then
562 fprintf(h, " extl_unregister_class(\"%s\", %s_exports);\n",
571 function write_header(h)
573 local u=string.upper(p)
575 /* Automatically generated by mkexports.lua */
576 #ifndef %sEXTL_EXPORTS_H
577 #define %sEXTL_EXPORTS_H
579 #include <libextl/extl.h>
581 extern bool %sregister_exports();
582 extern void %sunregister_exports();
584 #endif /* %sEXTL_EXPORTS_H */
592 -- Documentation output {{{
594 function tohuman(desc, objtype)
598 return desc2human[desc]
602 function texfriendly(name)
603 return string.gsub(name, "_", "-")
606 function texfriendly_typeormod(nm)
607 if string.find(nm, "A-Z") then
608 return "\\type{"..string.gsub(nm, '_', '\_').."}"
610 return "\\code{"..nm.."}"
614 function write_fndoc(h, fn, info)
618 fprintf(h, "\\begin{function}\n")
619 if info.exported_name then
620 fn=info.exported_name
624 if info.class~="global" then
625 fprintf(h, "\\index{%s@%s!", texfriendly(info.class),
626 texfriendly_typeormod(info.class));
627 fprintf(h, "%s@\\code{%s}}\n", texfriendly(fn), fn)
629 fprintf(h, "\\index{%s@\\code{%s}}\n", texfriendly(fn), fn)
632 if info.class~="global" then
633 fprintf(h, "\\hyperlabel{fn:%s.%s}", info.class, fn)
635 fprintf(h, "\\hyperlabel{fn:%s}", fn)
638 fprintf(h, "\\synopsis{")
640 h:write(tohuman(info.odesc, info.otype).." ")
643 if info.class~="global" then
644 fprintf(h, "%s.", info.class)
647 if not info.ivars then
649 fprintf(h, "%s%s}", fn, info.paramstr)
651 fprintf(h, "%s(", fn)
653 for i, varname in pairs(info.ivars) do
654 fprintf(h, comma .. "%s", tohuman(string.sub(info.idesc, i, i),
657 fprintf(h, " %s", varname)
663 h:write("\\begin{funcdesc}\n" .. trim(info.doc).. "\n\\end{funcdesc}\n")
664 fprintf(h, "\\end{function}\n\n")
668 function write_class_documentation(h, cls, in_subsect)
671 if not classes[cls] or not classes[cls].fns then
676 fprintf(h, "\n\n\\subsection{\\type{%s} functions}\n\n", cls)
679 for fn in pairs(classes[cls].fns) do
680 table.insert(sorted, fn)
684 for _, fn in ipairs(sorted) do
685 write_fndoc(h, fn, classes[cls].fns[fn])
690 function write_documentation(h)
693 write_class_documentation(h, module, false)
695 for cls in pairs(classes) do
697 table.insert(sorted, cls)
702 for _, cls in ipairs(sorted) do
703 write_class_documentation(h, cls, true)
722 Usage: libextl-mkexports [options] files...
724 Where options include:
736 if arg[i]=="-help" then
738 elseif arg[i]=="-mkdoc" then
740 elseif arg[i]=="-o" then
743 elseif arg[i]=="-h" then
746 elseif arg[i]=="-module" then
750 error("No module given")
752 elseif arg[i]=="-reexport" then
754 reexports[arg[i]]=true
756 table.insert(inputs, arg[i])
761 if table.getn(inputs)==0 then
765 for _, ifnam in pairs(inputs) do
766 h, err=io.open(ifnam, "r")
768 errorf("Could not open %s: %s", ifnam, err)
770 print("Scanning " .. ifnam .. " for exports.")
773 if string.find(ifnam, "%.lua$") then
775 parse_luadoc("\n" .. data .. "\n")
776 elseif string.find(ifnam, "%.c$") then
777 parse("\n" .. data .. "\n")
779 error('Unknown file')
785 outh, err=io.open(output_file, "w")
792 write_documentation(outh)
796 local hh, err=io.open(header_file, "w")