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>
512 /* quiet warnings for generated code */
514 #pragma GCC diagnostic ignored "-Wunused-parameter"
515 #pragma GCC diagnostic ignored "-Wunused-macros"
521 -- Write class infos and check that the class is implemented in the
523 for c, data in pairs(classes) do
524 if string.lower(c)==c then
527 fprintf(h, "EXTL_DEFCLASS(%s);\n", c)
528 if data.fns and not data.parent then
529 error(c..": Methods can only be registered if the class "
530 .. "is implemented in the module in question.")
535 -- Write L2 call handlers
536 for name, info in pairs(chnds) do
537 writechnd(h, name, info)
542 for cls, data in pairs(classes) do
544 -- Write function declarations
545 for fn in pairs(data.fns) do
546 fprintf(h, "extern void %s();\n", fn)
548 -- Write function table
549 write_class_fns(h, cls, data)
551 fprintf(h, "#define %s_exports NULL\n", cls)
555 fprintf(h, "bool %sregister_exports()\n{\n", pfx(module))
557 local sorted_classes=sort_classes()
559 for _, cls in pairs(sorted_classes) do
560 if cls=="global" then
561 fprintf(h, " if(!extl_register_functions(global_exports)) return FALSE;\n")
562 elseif classes[cls].module then
563 fprintf(h, " if(!extl_register_module(\"%s\", %s_exports)) return FALSE;\n",
565 elseif classes[cls].parent then
566 fprintf(h, " if(!extl_register_class(\"%s\", %s_exports, \"%s\")) return FALSE;\n",
567 cls, cls, classes[cls].parent)
571 fprintf(h, " return TRUE;\n}\n\nvoid %sunregister_exports()\n{\n",
574 for _, cls in pairs(sorted_classes) do
575 if cls=="global" then
576 fprintf(h, " extl_unregister_functions(global_exports);\n")
577 elseif classes[cls].module then
578 fprintf(h, " extl_unregister_module(\"%s\", %s_exports);\n",
580 elseif classes[cls].parent then
581 fprintf(h, " extl_unregister_class(\"%s\", %s_exports);\n",
590 function write_header(h)
592 local u=string.upper(p)
594 /* Automatically generated by mkexports.lua */
595 #ifndef %sEXTL_EXPORTS_H
596 #define %sEXTL_EXPORTS_H
598 #include <libextl/extl.h>
600 extern bool %sregister_exports();
601 extern void %sunregister_exports();
603 #endif /* %sEXTL_EXPORTS_H */
611 -- Documentation output {{{
613 function tohuman(desc, objtype)
617 return desc2human[desc]
621 function texfriendly(name)
622 return string.gsub(name, "_", "-")
625 function texfriendly_typeormod(nm)
626 if string.find(nm, "A-Z") then
627 return "\\type{"..string.gsub(nm, '_', '\\_').."}"
629 return "\\code{"..nm.."}"
633 function write_fndoc(h, fn, info)
637 fprintf(h, "\\begin{function}\n")
638 if info.exported_name then
639 fn=info.exported_name
643 if info.class~="global" then
644 fprintf(h, "\\index{%s@%s!", texfriendly(info.class),
645 texfriendly_typeormod(info.class));
646 fprintf(h, "%s@\\code{%s}}\n", texfriendly(fn), fn)
648 fprintf(h, "\\index{%s@\\code{%s}}\n", texfriendly(fn), fn)
651 if info.class~="global" then
652 fprintf(h, "\\hyperlabel{fn:%s.%s}", info.class, fn)
654 fprintf(h, "\\hyperlabel{fn:%s}", fn)
657 fprintf(h, "\\synopsis{")
659 h:write(tohuman(info.odesc, info.otype).." ")
662 if info.class~="global" then
663 fprintf(h, "%s.", info.class)
666 if not info.ivars then
668 fprintf(h, "%s%s}", fn, info.paramstr)
670 fprintf(h, "%s(", fn)
672 for i, varname in pairs(info.ivars) do
673 fprintf(h, comma .. "%s", tohuman(string.sub(info.idesc, i, i),
676 fprintf(h, " %s", varname)
682 h:write("\\begin{funcdesc}\n" .. trim(info.doc) ..
683 (info.safe and "\n This function is considered safe." or "") ..
684 "\n\\end{funcdesc}\n")
685 fprintf(h, "\\end{function}\n\n")
689 function write_class_documentation(h, cls, in_subsect)
692 if not classes[cls] or not classes[cls].fns then
697 fprintf(h, "\n\n\\subsection{\\type{%s} functions}\n\n", cls)
700 for fn in pairs(classes[cls].fns) do
701 table.insert(sorted, fn)
705 for _, fn in ipairs(sorted) do
706 write_fndoc(h, fn, classes[cls].fns[fn])
711 function write_documentation(h)
714 write_class_documentation(h, module, false)
716 for cls in pairs(classes) do
718 table.insert(sorted, cls)
723 for _, cls in ipairs(sorted) do
724 write_class_documentation(h, cls, true)
743 Usage: libextl-mkexports [options] files...
745 Where options include:
757 if arg[i]=="-help" then
759 elseif arg[i]=="-mkdoc" then
761 elseif arg[i]=="-o" then
764 elseif arg[i]=="-h" then
767 elseif arg[i]=="-module" then
771 error("No module given")
773 elseif arg[i]=="-reexport" then
775 reexports[arg[i]]=true
777 table.insert(inputs, arg[i])
786 for _, ifnam in pairs(inputs) do
787 h, err=io.open(ifnam, "r")
789 errorf("Could not open %s: %s", ifnam, err)
791 print("Scanning " .. ifnam .. " for exports.")
794 if string.find(ifnam, "%.lua$") then
796 parse_luadoc("\n" .. data .. "\n")
797 elseif string.find(ifnam, "%.c$") then
798 parse("\n" .. data .. "\n")
800 error('Unknown file')
806 outh, err=io.open(output_file, "w")
813 write_documentation(outh)
817 local hh, err=io.open(header_file, "w")