Released version 3-2015061300
[notion.git] / libextl / libextl-mkexports.in
blobca3ade2a087fc2f23e21294f20f2367fc5b8381d
1 #!LUA50
2 -- -*- mode: lua -*-
3 -- ion/mkexports.lua
4 -- 
5 -- Copyright (c) Tuomo Valkonen 2003-2005.
6 -- 
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.
12 -- 
13 -- This is a script to automatically generate exported function registration
14 -- code and documentation for those from C source.
15 -- 
16 -- The script can also parse documentation comments from Lua code.
17 -- 
19 -- Helper functions {{{
21 function errorf(fmt, ...)
22     error(string.format(fmt, ...), 2)
23 end
25 function matcherr(s)
26     error(string.format("Parse error in \"%s...\"", string.sub(s, 1, 50)), 2)
27 end
29 function fprintf(h, fmt, ...)
30     h:write(string.format(fmt, ...))
31 end
33 function trim(str)
34     return string.gsub(str, "^[%s\n]*(.-)[%s\n]*$", "%1")
35 end
37 -- }}}
40 -- Some conversion tables {{{
42 desc2ct={
43     ["v"]="void",
44     ["i"]="int",
45     ["d"]="double",
46     ["b"]="bool",
47     ["t"]="ExtlTab",
48     ["f"]="ExtlFn",
49     ["o"]="Obj*",
50     ["s"]="char*",
51     ["S"]="const char*",
54 ct2desc={
55     ["uint"] = "i",
58 for d, t in pairs(desc2ct) do
59     ct2desc[t]=d
60 end
62 desc2human={
63     ["v"]="void",
64     ["i"]="integer",
65     ["d"]="double",
66     ["b"]="bool",
67     ["t"]="table",
68     ["f"]="function",
69     ["o"]="object",
70     ["s"]="string",
71     ["S"]="string",
72     ["a"]="any value",
75 -- }}}
78 -- Parser {{{
80 local classes={}
81 local chnds={}
82 local reexports={}
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 .. "_"
88     
89     for i, t in ipairs(fnt.itypes) do
90         str=str .. "_" .. t
91     end
92     
93     chnds[str]={odesc=odesc, idesc=idesc, itypes=fnt.itypes}
94     fnt.chnd=str
95 end
97 function add_class(cls)
98     if cls~="Obj" and not classes[cls] then
99         classes[cls]={}
100     end
103 function sort_classes(cls)
104     local sorted={}
105     local inserted={}
106     
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)
111             end
112             inserted[cls]=true
113             table.insert(sorted, cls)
114         end
115     end
116     
117     for cls in pairs(classes) do
118         insert(cls)
119     end
120     
121     return sorted
124 function parse_type(t)
125     local desc, otype, varname="?", "", ""
126     
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, "%*", "* ")
133     
134     -- Check for const
135     local is_const=""
136     local s, e=string.find(t, "^const +")
137     if s then
138         is_const="const "
139         t=string.sub(t, e+1)
140     end
141     
142     -- Find variable name part
143     tn=t
144     s, e=string.find(tn, " ")
145     if s then
146         varname=string.sub(tn, e+1)
147         tn=string.sub(tn, 1, s-1)
148         assert(not string.find(varname, " "))
149     end
150     
151     -- Try to check for supported types
152     desc = ct2desc[is_const .. tn]
153     
154     if not desc or desc=="o" then
155         s, e=string.find(tn, "^[A-Z][%w_]*%*$")
156         if s then
157             desc="o"
158             otype=string.sub(tn, s, e-1)
159             add_class(otype)
160         else
161             errorf("Error parsing type from \"%s\"", t)
162         end
163     end
164     
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..'()')
171         return function()
172                 if done then return end
173                 local v,i = g()
174                 if s == '' or sep == '' then done = true return s end
175                 if v == nil then done = true return s:sub(lasti) end
176                 lasti = i
177                 return v
178         end
181 function parse(d)
182     local doc=nil
183     local safe=false
184     local untraced=false
185     
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, "%*/")
191         if not en then
192             errorf("Could not find end of comment in \"%s...\"",
193                    string.sub(s, 1, 50))
194         end
195         
196         s=string.sub(s, st+1, en-1)
197         s=string.gsub(s, "\n[%s]*%*", "\n")
198         doc=s
199     end
201     -- Handle EXTL_SAFE    
202     local function do_safe(s)
203         assert(not safe)
204         safe=true
205     end
207     -- Handle EXTL_UNTRACED
208     local function do_untraced(s)
209         assert(not untraced)
210         untraced=true
211     end
212     
213     local function do_do_export(cls, efn, ot, fn, param)
214         local odesc, otype=parse_type(ot)
215         local idesc, itypes, ivars="", {}, {}
216         
217         -- Parse arguments
218         param=string.sub(param, 2, -2)
219         if string.find(param, "[()]") then
220             errorf("Error: parameters to %s contain parantheses", fn)
221         end
222         param=trim(param)
223         if string.len(param)>0 then
224             for p in gsplit2(param, ',') do
225                 local spec, objtype, varname=parse_type(p)
226                 idesc=idesc .. spec
227                 table.insert(itypes, objtype)
228                 table.insert(ivars, varname)
229             end
230         end
231         
232         if cls=="?" then
233             if string.sub(idesc, 1, 1)~="o" then
234                 error("Invalid class for " .. fn)
235             end
236             cls=itypes[1]
237         end
238         
239         -- Generate call handler name
240         
241         local fninfo={
242             doc=doc,
243             safe=safe,
244             untraced=untraced,
245             odesc=odesc,
246             otype=otype,
247             idesc=idesc,
248             itypes=itypes,
249             ivars=ivars,
250             exported_name=efn,
251             class=cls,
252         }
253         
254         add_chnd(fninfo)
255         add_class(cls)
256         
257         if not classes[cls].fns then
258             classes[cls].fns={}
259         end
260         
261         assert(not classes[cls].fns[fn], "Function " .. fn .. " multiply defined!")
263         classes[cls].fns[fn]=fninfo
265         -- Reset
266         doc=nil
267         safe=false
268         untraced=false
269     end
271     -- Handle EXTL_EXPORT otype fn(args)
272     local function do_export(s)
273         local mdl, efn
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)
276         
277         if not st then matcherr(s) end
278         
279         if module=="global" or not module then
280             efn=fn
281             mdl=module
282         else
283             st, en, efn=string.find(fn, "^"..module.."_(.*)")
284             if efn then
285                 mdl=module
286             else
287                 for k in pairs(reexports) do
288                     st, en, efn=string.find(fn, "^"..k.."_(.*)")
289                     if efn then
290                         mdl=module
291                         break
292                     end
293                 end
294             end
295             
296             if not mdl then
297                 error('"'..fn..'" is not a valid function name of format '..
298                       'modulename_fnname.')
299             end
300         end
301         do_do_export(module, efn, ot, fn, param)
302     end
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)
311     end
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)
319     end
320     
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
325         add_class(cls)
326         classes[cls].parent=par
327     end
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
333         add_class(cls)
334         classes[cls].parent=par
335     end
336     
337     local lookfor={
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},
346     }
347     
348     do_parse(d, lookfor)
351 function do_parse(d, lookfor)
352     while true do
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])
356             if s and s<mins then
357                 mins, mine, minfn=s, e, lf[2]
358             end
359         end
360         
361         if not minfn then
362             return
363         end
364         
365         minfn(string.sub(d, mins))
366         d=string.sub(d, mine)
367     end
368 end    
370 -- }}}
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)
380         end
381         local doc, docl=""
382         while true do
383             st, en, docl=string.find(s, "^\n%s*%-%-([^\n]*\n)")
384             if not st then
385                 break
386             end
387             --print(docl)
388             doc=doc .. docl
389             s=string.sub(s, en)
390         end
391         
392         local fn, param
393         
394         st, en, fn, param=string.find(s, "^\n[%s\n]*function%s*([%w_:%.]+)%s*(%b())")
396         if not fn then
397             errorf("Syntax error while parsing \"%s\"",
398                    string.sub(s, 1, 50))
399         end
400         local cls, clsfn
401         st, en, cls, clsfn=string.find(fn, "^([^.]*)%.(.*)$")
402         
403         if cls and clsfn then
404             fn=clsfn
405         else
406             cls="global"
407         end
408         
409         fninfo={
410             doc=doc, 
411             paramstr=param,
412             class=cls,
413         }
414         
415         add_class(cls)
416         if not classes[cls].fns then
417             classes[cls].fns={}
418         end
419         classes[cls].fns[fn]=fninfo
420     end
421     
422     do_parse(d, {{"\n%-%-DOC", do_luadoc}})
424     
425 -- }}}
428 -- Export output {{{
430 function writechnd(h, name, info)
431     local oct=desc2ct[info.odesc]
432         
433     -- begin blockwrite
434     fprintf(h, [[
435 static bool %s(%s (*fn)(), ExtlL2Param *in, ExtlL2Param *out)
437 ]], name, oct)
438     -- end blockwrite
440     -- Generate type checking code
441     for k, t in pairs(info.itypes) do
442         if t~="" then
443             if k==1 then
444                 fprintf(h, "    if(!EXTL_CHKO1(in, %d, %s)) return FALSE;\n",
445                         k-1, t)
446             else
447                 fprintf(h, "    if(!EXTL_CHKO(in, %d, %s)) return FALSE;\n",
448                         k-1, t)
449             end
450         end
451     end
453     -- Generate function call code
454     if info.odesc=="v" then
455         fprintf(h, "    fn(")
456     else
457         fprintf(h, "    out[0].%s=fn(", info.odesc)
458     end
459     
460     comma=""
461     for k=1, string.len(info.idesc) do
462         fprintf(h, comma .. "in[%d].%s", k-1, string.sub(info.idesc, k, k))
463         comma=", "
464     end
465     fprintf(h, ");\n    return TRUE;\n}\n")
466 end    
468 function bool2str4c(b)
469     return (b and "TRUE" or "FALSE")
470 end        
472 function write_class_fns(h, cls, data)
473     fprintf(h, "\n\nstatic ExtlExportedFnSpec %s_exports[] = {\n", cls)
474     
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 .. '"'
479         end
480         
481         if info.idesc~="" then
482             ids='"' .. info.idesc .. '"'
483         end
484         
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))
489     end
490     
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
497         return ""
498     else
499         return modname.."_"
500     end
504 function write_exports(h)
505     
506     -- begin blockwrite
507     h:write([[
508 /* Automatically generated by mkexports.lua */
509 #include <libextl/extl.h>
510 #include <libextl/private.h>
512 /* quiet warnings for generated code */
513 #ifdef __GNUC__
514 #pragma GCC diagnostic ignored "-Wunused-parameter"
515 #pragma GCC diagnostic ignored "-Wunused-macros"
516 #endif
519     -- end blockwrite
521     -- Write class infos and check that the class is implemented in the 
522     -- module.
523     for c, data in pairs(classes) do
524         if string.lower(c)==c then
525             data.module=true
526         else
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.")
531             end
532         end
533     end
534     
535     -- Write L2 call handlers
536     for name, info in pairs(chnds) do
537         writechnd(h, name, info)
538     end
539     
540     fprintf(h, "\n")
541     
542     for cls, data in pairs(classes) do
543         if data.fns then
544             -- Write function declarations
545             for fn in pairs(data.fns) do
546                 fprintf(h, "extern void %s();\n", fn)
547             end
548             -- Write function table
549             write_class_fns(h, cls, data)
550         else
551             fprintf(h, "#define %s_exports NULL\n", cls)
552         end
553     end
554     
555     fprintf(h, "bool %sregister_exports()\n{\n", pfx(module))
557     local sorted_classes=sort_classes()
558     
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", 
564                     cls, cls)
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)
568         end
569     end
571     fprintf(h, "    return TRUE;\n}\n\nvoid %sunregister_exports()\n{\n", 
572             pfx(module))
573     
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", 
579                     cls, cls)
580         elseif classes[cls].parent then
581             fprintf(h, "    extl_unregister_class(\"%s\", %s_exports);\n",
582                     cls, cls)
583         end
584     end
585     
586     fprintf(h, "}\n\n")
590 function write_header(h)
591     local p=pfx(module)
592     local u=string.upper(p)
593     fprintf(h, [[
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 */
605 ]], u, u, p, p, u)
608 -- }}}
611 -- Documentation output {{{
613 function tohuman(desc, objtype)
614     if objtype~="" then
615         return objtype
616     else
617         return desc2human[desc]
618     end
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, '_', '\\_').."}"
628     else
629         return "\\code{"..nm.."}"
630     end
633 function write_fndoc(h, fn, info)
634     if not info.doc then
635         return
636     end
637     fprintf(h, "\\begin{function}\n")
638     if info.exported_name then
639         fn=info.exported_name
640     end
641     
642     --[[
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)
647     end
648     fprintf(h, "\\index{%s@\\code{%s}}\n", texfriendly(fn), fn)
649     ]]
650     
651     if info.class~="global" then
652         fprintf(h, "\\hyperlabel{fn:%s.%s}", info.class, fn)
653     else
654         fprintf(h, "\\hyperlabel{fn:%s}", fn)
655     end
656     
657     fprintf(h, "\\synopsis{")
658     if info.odesc then
659         h:write(tohuman(info.odesc, info.otype).." ")
660     end
661     
662     if info.class~="global" then
663         fprintf(h, "%s.", info.class)
664     end
665     
666     if not info.ivars then
667         -- Lua input
668         fprintf(h, "%s%s}", fn, info.paramstr)
669     else
670         fprintf(h, "%s(", fn)
671         local comma=""
672         for i, varname in pairs(info.ivars) do
673             fprintf(h, comma .. "%s", tohuman(string.sub(info.idesc, i, i),
674                                               info.itypes[i]))
675             if varname then
676                 fprintf(h, " %s", varname)
677             end
678             comma=", "
679         end
680         fprintf(h, ")}\n")
681     end
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)
690     sorted={}
691     
692     if not classes[cls] or not classes[cls].fns then
693         return
694     end
695     
696     if in_subsect then
697         fprintf(h, "\n\n\\subsection{\\type{%s} functions}\n\n", cls)
698     end
700     for fn in pairs(classes[cls].fns) do
701         table.insert(sorted, fn)
702     end
703     table.sort(sorted)
704     
705     for _, fn in ipairs(sorted) do
706         write_fndoc(h, fn, classes[cls].fns[fn])
707     end
711 function write_documentation(h)
712     sorted={}
713     
714     write_class_documentation(h, module, false)
715     
716     for cls in pairs(classes) do
717         if cls~=module then
718             table.insert(sorted, cls)
719         end
720     end
721     table.sort(sorted)
722     
723     for _, cls in ipairs(sorted) do
724         write_class_documentation(h, cls, true)
725     end
728 -- }}}
731 -- main {{{
733 inputs={}
734 outh=io.stdout
735 header_file=nil
736 output_file=nil
737 make_docs=false
738 module="global"
741 function usage()
742     print([[
743 Usage: libextl-mkexports [options] files...
745 Where options include:
746     -mkdoc
747     -help
748     -o outfile
749     -h header
750     -module module
751     -reexport module
753     os.exit()
756 while arg[i] do
757     if arg[i]=="-help" then
758         usage()
759     elseif arg[i]=="-mkdoc" then
760         make_docs=true
761     elseif arg[i]=="-o" then
762         i=i+1
763         output_file=arg[i]
764     elseif arg[i]=="-h" then
765         i=i+1
766         header_file=arg[i]
767     elseif arg[i]=="-module" then
768         i=i+1
769         module=arg[i]
770         if not module then
771             error("No module given")
772         end
773     elseif arg[i]=="-reexport" then
774         i=i+1
775         reexports[arg[i]]=true
776     else
777         table.insert(inputs, arg[i])
778     end
779     i=i+1
782 if #inputs==0 then
783     usage()
786 for _, ifnam in pairs(inputs) do
787     h, err=io.open(ifnam, "r")
788     if not h then
789             errorf("Could not open %s: %s", ifnam, err)
790     end
791     print("Scanning " .. ifnam .. " for exports.")
792     data=h:read("*a")
793     h:close()
794     if string.find(ifnam, "%.lua$") then
795         assert(make_docs)
796         parse_luadoc("\n" .. data .. "\n")
797     elseif string.find(ifnam, "%.c$") then
798         parse("\n" .. data .. "\n")
799     else
800         error('Unknown file')
801     end
802     
805 if output_file then        
806     outh, err=io.open(output_file, "w")
807     if not outh then
808         error(err)
809     end
812 if make_docs then
813     write_documentation(outh)
814 else
815     write_exports(outh)
816     if header_file then
817         local hh, err=io.open(header_file, "w")
818         if not hh then
819             error(err)
820         end
821         write_header(hh)
822         hh:close()
823     end
826 -- }}}