Merge 'remotes/dkogan/merged_submodules'
[notion.git] / libextl / libextl-mkexports.in
blobae935e242a808c2edf06484b070b9093031c5bc9
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>
513     -- end blockwrite
515     -- Write class infos and check that the class is implemented in the 
516     -- module.
517     for c, data in pairs(classes) do
518         if string.lower(c)==c then
519             data.module=true
520         else
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.")
525             end
526         end
527     end
528     
529     -- Write L2 call handlers
530     for name, info in pairs(chnds) do
531         writechnd(h, name, info)
532     end
533     
534     fprintf(h, "\n")
535     
536     for cls, data in pairs(classes) do
537         if data.fns then
538             -- Write function declarations
539             for fn in pairs(data.fns) do
540                 fprintf(h, "extern void %s();\n", fn)
541             end
542             -- Write function table
543             write_class_fns(h, cls, data)
544         else
545             fprintf(h, "#define %s_exports NULL\n", cls)
546         end
547     end
548     
549     fprintf(h, "bool %sregister_exports()\n{\n", pfx(module))
551     local sorted_classes=sort_classes()
552     
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", 
558                     cls, cls)
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)
562         end
563     end
565     fprintf(h, "    return TRUE;\n}\n\nvoid %sunregister_exports()\n{\n", 
566             pfx(module))
567     
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", 
573                     cls, cls)
574         elseif classes[cls].parent then
575             fprintf(h, "    extl_unregister_class(\"%s\", %s_exports);\n",
576                     cls, cls)
577         end
578     end
579     
580     fprintf(h, "}\n\n")
584 function write_header(h)
585     local p=pfx(module)
586     local u=string.upper(p)
587     fprintf(h, [[
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 */
599 ]], u, u, p, p, u)
602 -- }}}
605 -- Documentation output {{{
607 function tohuman(desc, objtype)
608     if objtype~="" then
609         return objtype
610     else
611         return desc2human[desc]
612     end
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, '_', '\\_').."}"
622     else
623         return "\\code{"..nm.."}"
624     end
627 function write_fndoc(h, fn, info)
628     if not info.doc then
629         return
630     end
631     fprintf(h, "\\begin{function}\n")
632     if info.exported_name then
633         fn=info.exported_name
634     end
635     
636     --[[
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)
641     end
642     fprintf(h, "\\index{%s@\\code{%s}}\n", texfriendly(fn), fn)
643     ]]
644     
645     if info.class~="global" then
646         fprintf(h, "\\hyperlabel{fn:%s.%s}", info.class, fn)
647     else
648         fprintf(h, "\\hyperlabel{fn:%s}", fn)
649     end
650     
651     fprintf(h, "\\synopsis{")
652     if info.odesc then
653         h:write(tohuman(info.odesc, info.otype).." ")
654     end
655     
656     if info.class~="global" then
657         fprintf(h, "%s.", info.class)
658     end
659     
660     if not info.ivars then
661         -- Lua input
662         fprintf(h, "%s%s}", fn, info.paramstr)
663     else
664         fprintf(h, "%s(", fn)
665         local comma=""
666         for i, varname in pairs(info.ivars) do
667             fprintf(h, comma .. "%s", tohuman(string.sub(info.idesc, i, i),
668                                               info.itypes[i]))
669             if varname then
670                 fprintf(h, " %s", varname)
671             end
672             comma=", "
673         end
674         fprintf(h, ")}\n")
675     end
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)
684     sorted={}
685     
686     if not classes[cls] or not classes[cls].fns then
687         return
688     end
689     
690     if in_subsect then
691         fprintf(h, "\n\n\\subsection{\\type{%s} functions}\n\n", cls)
692     end
694     for fn in pairs(classes[cls].fns) do
695         table.insert(sorted, fn)
696     end
697     table.sort(sorted)
698     
699     for _, fn in ipairs(sorted) do
700         write_fndoc(h, fn, classes[cls].fns[fn])
701     end
705 function write_documentation(h)
706     sorted={}
707     
708     write_class_documentation(h, module, false)
709     
710     for cls in pairs(classes) do
711         if cls~=module then
712             table.insert(sorted, cls)
713         end
714     end
715     table.sort(sorted)
716     
717     for _, cls in ipairs(sorted) do
718         write_class_documentation(h, cls, true)
719     end
722 -- }}}
725 -- main {{{
727 inputs={}
728 outh=io.stdout
729 header_file=nil
730 output_file=nil
731 make_docs=false
732 module="global"
735 function usage()
736     print([[
737 Usage: libextl-mkexports [options] files...
739 Where options include:
740     -mkdoc
741     -help
742     -o outfile
743     -h header
744     -module module
745     -reexport module
747     os.exit()
750 while arg[i] do
751     if arg[i]=="-help" then
752         usage()
753     elseif arg[i]=="-mkdoc" then
754         make_docs=true
755     elseif arg[i]=="-o" then
756         i=i+1
757         output_file=arg[i]
758     elseif arg[i]=="-h" then
759         i=i+1
760         header_file=arg[i]
761     elseif arg[i]=="-module" then
762         i=i+1
763         module=arg[i]
764         if not module then
765             error("No module given")
766         end
767     elseif arg[i]=="-reexport" then
768         i=i+1
769         reexports[arg[i]]=true
770     else
771         table.insert(inputs, arg[i])
772     end
773     i=i+1
776 if #inputs==0 then
777     usage()
780 for _, ifnam in pairs(inputs) do
781     h, err=io.open(ifnam, "r")
782     if not h then
783             errorf("Could not open %s: %s", ifnam, err)
784     end
785     print("Scanning " .. ifnam .. " for exports.")
786     data=h:read("*a")
787     h:close()
788     if string.find(ifnam, "%.lua$") then
789         assert(make_docs)
790         parse_luadoc("\n" .. data .. "\n")
791     elseif string.find(ifnam, "%.c$") then
792         parse("\n" .. data .. "\n")
793     else
794         error('Unknown file')
795     end
796     
799 if output_file then        
800     outh, err=io.open(output_file, "w")
801     if not outh then
802         error(err)
803     end
806 if make_docs then
807     write_documentation(outh)
808 else
809     write_exports(outh)
810     if header_file then
811         local hh, err=io.open(header_file, "w")
812         if not hh then
813             error(err)
814         end
815         write_header(hh)
816         hh:close()
817     end
820 -- }}}