b33cf84c0db16f4ca8297ceba4f08f4f0087594a
[apps/madmutt.git] / tools / cpkg2c.mll
1 (*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or (at
5  *  your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful, but
8  *  WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
15  *  MA 02110-1301, USA.
16  *
17  *  Copyright © 2007 Pierre Habouzit
18  *)
19 {
20   open Printf
21   module L = Lexing
22   module H = Hashtbl
23   module B = Buffer
24
25   type 'a anchor   = ('a * string * int)
26   let t1 (a, _, _) = a
27
28   let die lpos fpos s =
29     output_string stderr (sprintf "%s:%d: %s\n" fpos lpos s);
30     exit 1
31
32   let put_line = printf "#line %d \"%s\"\n"
33
34 (* @types related {{{ *)
35
36   type typedef =
37     { lpos: int; fpos: string;
38       ctype: string anchor;
39       check: string anchor;
40       push : string anchor;
41       ctor : string anchor option;
42       dtor : string anchor option;
43     }
44   let typedef0 =
45     { lpos  = 0; fpos = "";
46       ctype = ("", "", 0);
47       check = ("", "", 0);
48       push  = ("", "", 0);
49       ctor  = None;
50       dtor = None
51     }
52   let types = H.create 1031
53
54
55   let (@:=) id  t =
56     List.iter (function ((s,_,_), x) -> if String.length s == 0 then
57       die t.lpos t.fpos (sprintf "type `%s' has no `%s' set" id x))
58     [(t.ctype, "ctype"); (t.check, "check"); (t.push, "push")];
59     H.add types id t
60
61 (* }}} *)
62 (* @packages related {{{ *)
63
64   type typinst = bool * typedef
65
66   type members =
67     { typ: typinst; mname: string; init: string }
68
69   type methods =
70     { rettype: typinst list; args: (typinst * string) list;
71       fname: string; body: string }
72
73   type package =
74     { line: int; file: string; name: string;
75       members: members anchor list;
76       methods: methods anchor list;
77       static: bool;
78     }
79
80   let newpkg lpos fpos name static =
81     { static = static; line = lpos; file = fpos; name = name; members = []; methods = []; }
82
83   let type_find lpos fpos t =
84     try Hashtbl.find types t
85     with Not_found -> die lpos fpos (sprintf "Unknown type `%s'" t)
86
87   let parse_rettype lpos fpos s =
88     let aux t =
89       match Str.split (Str.regexp "[ \t]+") t with
90       | ["const"; t] -> (true,  type_find lpos fpos t)
91       | [t]          -> (false, type_find lpos fpos t)
92       | _ -> assert false
93     in
94     match s with
95     | "void" -> []
96     | s      -> List.map aux (Str.split (Str.regexp "[ \t]+,[ \t]+") s)
97
98   let parse_args lpos fpos s =
99     let aux t =
100       match Str.split (Str.regexp "[ \t]+") t with
101       | ["const"; t; n] -> ((true,  type_find lpos fpos t), n)
102       | [t; n]          -> ((false, type_find lpos fpos t), n)
103       | _ -> assert false
104     in
105     match s with
106     | "void" -> []
107     | s      -> List.map aux (Str.split (Str.regexp "[ \t]+,[ \t]+") s)
108
109 (* }}} *)
110 (* parsing helpers {{{ *)
111
112   type stanza = Buf of string anchor | Pkg of package
113
114   let nextl lexbuf =
115     let pos = lexbuf.L.lex_curr_p in
116     lexbuf.L.lex_curr_p <-
117       { pos with
118         L.pos_lnum = pos.L.pos_lnum + 1;
119         L.pos_bol  = pos.L.pos_cnum; };
120     lexbuf
121   ;;
122
123   let lnum lexbuf = lexbuf.L.lex_curr_p.L.pos_lnum
124
125   let (@.)  buf s = B.add_string buf s; buf
126   let (@<)  buf c = B.add_char   buf c; buf
127
128   let getchunk lpos fpos buf lexbuf =
129     let res = B.contents buf in
130     B.clear buf;
131     Buf(res, fpos, lpos)
132
133   let startchunk f fpos lexbuf =
134     f (lnum lexbuf) fpos (B.create 4096) lexbuf
135
136   let newchunk f lpos fpos buf lexbuf =
137     let res = getchunk lpos fpos buf lexbuf in
138     res::(f (lnum lexbuf, fpos) buf lexbuf)
139
140   let find_import relpath file =
141     if Filename.is_relative file then
142       match Filename.dirname relpath with
143       | "." -> file
144       | s   -> Filename.concat s file
145     else
146       file
147
148 (* }}} *)
149 }
150
151 (* regex aliases {{{ *)
152
153 let to_eol = [^'\n']* '\n'
154 let ident  = ['a'-'z''A'-'Z''_']['a'-'z''A'-'Z''_''0'-'9']*
155 let sp     = [' ''\t']
156
157 let typdecl = sp* ("const" sp+)? ident sp*
158
159 (* }}} *)
160 (* entry point {{{ *)
161
162 rule cLine lpos fpos buf = parse
163 | sp* "@type" sp+ (ident as id) sp* (':' sp* (ident as id0) sp*)? '{'
164                     {
165                       let line = lnum lexbuf in
166                       let t0 = match id0 with
167                       | None     -> typedef0
168                       | Some id0 ->
169                           try H.find types id0
170                           with Not_found ->
171                             die line fpos (sprintf "Uknown type `%s'" id0)
172                       in try
173                         let t = H.find types id in
174                         die line fpos
175                           (sprintf "type `%s' already defined at %s:%d"
176                             id t.fpos t.lpos)
177                       with Not_found ->
178                         (getchunk lpos fpos buf lexbuf) :: (
179                           ext_type id {t0 with lpos = line; fpos = fpos}
180                             lexbuf
181                         )
182                     }
183 | sp* "@import" sp+ '"' ([^'"''\n']* as file) '"' sp* '\n'
184                     {
185                       let file = find_import fpos file in
186                       let _ = startchunk cLine file
187                                 (L.from_channel (open_in file)) in
188                       cLine lpos fpos (buf @< '\n') (nextl lexbuf)
189                     }
190 | sp* ("@package"|"@static_package" as kw) sp+ (ident as pkg) sp* '{'
191                     {
192                       let a = getchunk lpos fpos buf lexbuf in
193                       let line = lnum lexbuf in
194                       a::(ext_pkg (newpkg line fpos pkg
195                                     (kw = "@static_package")) lexbuf)
196                     }
197 | sp* '@'           { die (lnum lexbuf) fpos "Syntax error" }
198 | sp* '#' as s      { let buf = cPP (buf @. s) lexbuf in
199                       cLine lpos fpos buf lexbuf }
200 | ""                { let buf = cCode buf lexbuf in
201                       cLine lpos fpos buf lexbuf }
202 | eof               { [getchunk lpos fpos buf lexbuf] }
203
204 (* }}} *)
205 (* cCode block parser {{{ *)
206
207 and cCode buf = parse
208 | '\n'    as c      { let _ = nextl lexbuf in buf @< c }
209 | '"'     as c      { let buf = cString (buf @< c) lexbuf in
210                       cCode buf lexbuf }
211 | "/*"    as s      { let buf = cComment (buf @. s) lexbuf in
212                       cCode buf lexbuf }
213 | "//" to_eol as s  { cCode (buf @. s) (nextl lexbuf) }
214 | "'\"'"  as s      { cCode (buf @. s) lexbuf }
215 | _       as c      { cCode (buf @< c) lexbuf }
216
217 (* }}} *)
218 (* helper rules: comment, string, cPP {{{ *)
219
220 and cComment buf = parse
221 | "*/"    as s { buf @. s }
222 | '\n'    as c { cComment (buf @< c) (nextl lexbuf) }
223 | _       as c { cComment (buf @< c) lexbuf }
224
225 and cString buf = parse
226 | '"'     as c { buf @< c }
227 | "\\\""  as s { cString (buf @. s) lexbuf }
228 | "\\\n"  as s { cString (buf @. s) (nextl lexbuf) }
229 | [^'\n'] as c { cString (buf @< c) lexbuf }
230
231 and cPP buf = parse
232 | [^'\n']* "\\\n" as s { cPP (buf @. s) (nextl lexbuf) }
233 | to_eol          as s { let _ = nextl lexbuf in buf @. s }
234
235 (* }}} *)
236 (* parse @type {{{ *)
237
238 and ext_type id typ = parse
239 | sp+
240 | "//" to_eol       { ext_type id typ lexbuf }
241 | '\n'              { ext_type id typ (nextl lexbuf) }
242 | "/*"              { let _ = cComment (B.create 1024) lexbuf in
243                       ext_type id typ lexbuf }
244 | '.' (ident as member) sp* '=' sp* ([^';''\n']+ as s) ';'
245                     {
246                       ext_type id (
247                         let do_anch s = s, typ.fpos, lnum lexbuf in
248                         match member with
249                         | "ctype" -> {typ with ctype = do_anch s}
250                         | "check" -> {typ with check = do_anch s}
251                         | "push"  -> {typ with push  = do_anch s}
252                         | "ctor"  -> {typ with ctor  = Some (do_anch s)}
253                         | "dtor"  -> {typ with dtor  = Some (do_anch s)}
254                         | _       ->
255                             die (lnum lexbuf) typ.fpos
256                               (sprintf "Unknown directive `%s'" member)
257                       ) lexbuf
258                     }
259 | '}' sp* ';' (sp* '\n' as s)?
260                     { id @:= typ; startchunk cLine typ.fpos
261                       (if s = None then lexbuf else nextl lexbuf) }
262 | ""                { die (lnum lexbuf) typ.fpos "Syntax error" }
263
264 (* }}} *)
265 (* parse @package {{{ *)
266
267 and ext_pkg pkg = parse
268 | sp+
269 | "//" to_eol       { ext_pkg pkg lexbuf }
270 | '\n'              { ext_pkg pkg (nextl lexbuf) }
271 | "/*"              { let _ = cComment (B.create 1024) lexbuf in
272                       ext_pkg pkg lexbuf }
273 | ("const" sp+ as const)? (ident as typ) sp+
274   (ident as member) sp* '=' sp* ([^';''\n']* as init)';'
275                     {
276                       let m = { typ   = const != None,
277                                         type_find (lnum lexbuf) pkg.file typ;
278                                 mname = member;
279                                 init  = init; }, pkg.file, lnum lexbuf in
280                       ext_pkg {pkg with members = m::pkg.members} lexbuf
281                     }
282 | '(' ((typdecl ',')* typdecl as ret) ')' sp*
283    (ident as fname)
284    '('((typdecl ident sp* ',')* typdecl ident sp* as args)')' sp*
285 | (typdecl as ret) (ident as fname)
286    '('((typdecl ident sp* ',')* typdecl ident sp* as args)')' sp*
287                     {
288                       let rettype = parse_rettype (lnum lexbuf) pkg.file ret in
289                       let args    = parse_args    (lnum lexbuf) pkg.file args in
290                       let body    = ext_body pkg (B.create 1024) lexbuf in
291                       let m       = { rettype = rettype;
292                                       args = args;
293                                       fname = fname;
294                                       body = body }, pkg.file, lnum lexbuf in
295                       ext_pkg {pkg with methods = m::pkg.methods} lexbuf
296                     }
297 | '}' sp* ';' (sp* '\n' as s)?
298                     { Pkg({pkg with members = List.rev(pkg.members)}) :: (
299                         startchunk cLine pkg.file
300                         (if s = None then lexbuf else nextl lexbuf)
301                       )
302                     }
303 | ""                { die (lnum lexbuf) pkg.file "Syntax error" }
304
305 and ext_body pkg buf = parse
306 | sp+         as s
307 | "//" to_eol as s  { ext_body pkg (buf @. s) lexbuf }
308 | '\n'        as c  { ext_body pkg (buf @< c) lexbuf }
309 | "/*"              { let buf = cComment (buf @. "/*") lexbuf in
310                       ext_body pkg buf lexbuf }
311 | ';'               { B.contents buf }
312 | '{'         as c  { let buf = ext_bodycode (buf @< c) lexbuf in
313                       ext_body pkg buf lexbuf }
314 | ""                { die (lnum lexbuf) pkg.file "Syntax error" }
315
316 and ext_bodycode buf = parse
317 | '\n'    as c      { ext_bodycode (buf @< c) (nextl lexbuf) }
318 | '"'     as c      { let buf = cString (buf @< c) lexbuf in
319                       ext_bodycode buf lexbuf }
320 | "/*"    as s      { let buf = cComment (buf @. s) lexbuf in
321                       ext_bodycode buf lexbuf }
322 | "//" to_eol as s  { ext_bodycode (buf @. s) (nextl lexbuf) }
323 | "'\"'"  as s      { ext_bodycode (buf @. s) lexbuf }
324 | '{'     as c      { let buf = ext_bodycode (buf @< c) lexbuf in ext_bodycode buf lexbuf }
325 | '}'     as c      { buf @< c }
326 | _       as c      { ext_bodycode (buf @< c) lexbuf }
327
328 (* }}} *)
329
330 {
331 (* templating functions {{{ *)
332
333   let upper = String.uppercase
334   let tplize tpl v =
335     Str.global_replace (Str.regexp_string "$L") "L" (
336       Str.global_replace (Str.regexp_string "$$") v tpl
337     )
338
339   let dump_struct_type begwith pkg endwith =
340     printf "%sstruct luaM_%s_t {\n" begwith pkg.name;
341     List.iter (function (m, f, l) ->
342       put_line l f;
343       let ctype = t1 (snd m.typ).ctype in
344       print_string "    ";
345       if fst m.typ then print_string "const ";
346       let i = try String.index ctype ':' with Not_found -> String.length ctype in
347       printf "%s %s%s;\n" (Str.string_before ctype i)
348         m.mname (Str.string_after ctype i)
349     ) pkg.members;
350     print_endline ("}" ^ endwith)
351
352   let do_h =
353     let do_h_aux = function
354       | Buf _ -> ()
355       | Pkg pkg ->
356           printf "\n#ifndef MUTT_LUA_%s_H\n" (upper pkg.name);
357           printf "#define MUTT_LUA_%s_H\n\n" (upper pkg.name);
358           if not pkg.static then (
359             dump_struct_type "" pkg ";";
360             printf "extern struct luaM_%s_t %s;\n" pkg.name pkg.name;
361           );
362           printf "\nint luaopen_%s(lua_State *L);\n\n" pkg.name;
363           printf "#endif /* MUTT_LUA_%s_H */\n" (upper pkg.name);
364     in List.iter do_h_aux
365
366   let do_func pkg (fn, f, l) = 
367     (* return inline function *)
368     printf "\nstatic int luaM_ret_%s_%s(lua_State *L" pkg.name fn.fname;
369     let retlen = List.fold_left (fun i t ->
370       let i = i + 1 in
371       match t with
372       | true, typ -> printf ", const %s luaM_x%d" (t1 typ.ctype) i; i
373       | false, typ -> printf ", %s luaM_x%d" (t1 typ.ctype) i; i
374       ) 0 fn.rettype in
375     printf ")\n{\n";
376     ignore (List.fold_left (fun i (const, typ) ->
377       let i = i + 1 in
378       let (p, f, l) = typ.push in
379       put_line l f;
380       printf "    %s;\n" (tplize p (sprintf "luaM_x%d" i));
381       if not const then (
382         match typ.dtor with
383         | None             -> ()
384         | Some(dtor, f, l) ->
385             put_line l f;
386             printf "    %s;\n" (tplize dtor (sprintf "&luaM_x%d" i))
387       ); i
388     ) 0 fn.rettype) ;
389     printf "    return %d;\n}\n" retlen;
390
391     (* main function *)
392     printf "\nstatic int luaM_%s_%s(lua_State *L)\n{\n" pkg.name fn.fname;
393     ignore (List.fold_left (fun i ((const, typ), name) ->
394       let i = i + 1 in
395       let ctype = t1 typ.ctype in
396       if const then (
397         let (c, f, l) = typ.check in
398         put_line l f;
399         printf "    const %s %s = %s;\n" ctype name (tplize c (string_of_int i))
400       ) else (
401         match typ.ctor with
402         | None ->
403           let (c, f, l) = typ.check in
404           put_line l f;
405           printf "    %s %s = %s;\n" ctype name (tplize c (string_of_int i))
406         | Some (ctor, f, l) ->
407           let v =
408             let c, f, l = typ.check in
409             tplize (sprintf "\n#line %d \"%s\"\n        %s" l f c)
410               (string_of_int i)
411           in
412           put_line l f;
413           printf "    %s %s = %s;\n" ctype name (tplize ctor v)
414       ); i
415     ) 0 fn.args);
416     printf "\n#define RAISE(s)  luaL_error(L, (s))\n";
417     if fn.rettype = [] then (
418       printf "#define RETURN return luaM_ret_%s_%s(L)\n" pkg.name fn.fname
419     ) else (
420       printf "#define RETURN(luaM_x1";
421       for i = 2 to retlen do printf ", luaM_x%d" i done;
422       printf ") \\\n        return luaM_ret_%s_%s(L" pkg.name fn.fname;
423       for i = 1 to retlen do printf ", luaM_x%d" i done;
424       printf ")\n"
425     );
426     put_line l f;
427     printf "    %s\n#undef RAISE\n#undef RETURN\n}\n" fn.body
428
429   let do_c =
430     let do_c_aux = function
431       | Buf (s, f, l) -> put_line l f; print_string s
432       | Pkg pkg       ->
433           (* dump struct const init *)
434           (if pkg.static then
435             dump_struct_type "static " pkg (sprintf " %s = {\n" pkg.name)
436           else
437             printf "struct luaM_%s_t %s = {\n" pkg.name pkg.name
438           );
439           List.iter (function (m, f, l) ->
440               put_line l f;
441               printf "    %s,\n" (if fst m.typ then m.init else "0")
442             ) pkg.members;
443           printf "};\n";
444
445           (* dump struct init func *)
446           printf "\nstatic void %s_init(void)\n{\n" pkg.name;
447           List.iter (function (m, f, l) ->
448             if not (fst m.typ) then
449               printf "#line %d \"%s\"\n    %s.%s = %s;\n"
450               l f pkg.name m.mname m.init) pkg.members;
451           printf "};\n\n";
452
453           (* dump __index *)
454           printf "static int luaM_%s_index(lua_State *L)\n{\n" pkg.name;
455           printf "    const char *idx = luaL_checkstring(L, 2);\n\n";
456           printf "    switch (mlua_which_token(idx, -1)) {\n";
457           printf "      default:\n";
458           List.iter (function (m, _, _) ->
459             printf "      case LTK_%s:\n" (upper m.mname);
460             let push, f, l = (snd m.typ).push in
461             put_line l f;
462             printf "        %s;\n" (tplize push (sprintf "%s.%s" pkg.name m.mname));
463             printf "        return 1;\n"
464           ) pkg.members;
465           printf "        lua_rawget(L, lua_upvalueindex(2));\n";
466           printf "        return 1;\n";
467           printf "    }\n}\n\n";
468
469           (* dump __newindex *)
470           printf "static int luaM_%s_newindex(lua_State *L)\n{\n" pkg.name;
471           printf "    const char *idx = luaL_checkstring(L, 2);\n\n";
472           printf "    switch (mlua_which_token(idx, -1)) {\n";
473           List.iter (function (m, _, _) ->
474             match m.typ with
475             | true, _ -> ()
476             | false, t ->
477               printf "      case LTK_%s:\n" (upper m.mname);
478               (match t.dtor with
479               | None -> ()
480               | Some (dtor, f, l) ->
481                   put_line l f;
482                   printf "        %s;\n" (tplize dtor (sprintf "&%s.%s" pkg.name m.mname))
483               );
484               (match t.ctor with
485               | None ->
486                   let (c, f, l) = t.check in
487                   put_line l f;
488                   printf "        %s.%s = %s;\n" pkg.name m.mname (tplize c "3")
489               | Some (ctor, f, l) ->
490                   let v =
491                     let c, f, l = t.check in
492                     tplize (sprintf "\n#line %d \"%s\"\n            %s" l f c) "3"
493                   in
494                   put_line l f;
495                   printf "        %s.%s = %s;\n" pkg.name m.mname (tplize ctor v)
496               );
497               printf "        return 1;\n"
498           ) pkg.members;
499           printf "      default:\n";
500           printf "        return 1;\n";
501           printf "    }\n}\n";
502
503           (* dump methods *)
504           List.iter (do_func pkg) pkg.methods;
505           printf "\nstatic const luaL_reg luaM_%s_methods[] = {\n" pkg.name;
506           List.iter (function (f, _, _) ->
507               printf "    { \"%s\", luaM_%s_%s },\n" f.fname pkg.name f.fname)
508             pkg.methods;
509           print_string (Str.global_replace (Str.regexp "%s") pkg.name
510 "    { NULL, NULL },
511 };
512
513 int luaopen_%s(lua_State *L)
514 {
515     int mt, methods;
516
517     %s_init();
518
519     /* create methods table, add it the the table of globals */
520     luaL_openlib(L, \"%s\", luaM_%s_methods, 0);
521     methods = lua_gettop(L);
522
523     /* create metatable for %s, add it to the registry */
524     luaL_newmetatable(L, \"%s\");
525     mt = lua_gettop(L);
526
527     lua_pushliteral(L, \"%s\");
528     lua_pushvalue(L, mt);                       /* upvalue 1         */
529     lua_pushvalue(L, methods);                  /* upvalue 2         */
530     lua_pushcclosure(L, &luaM_%s_index, 2);
531     lua_rawset(L, mt);                          /* set mt.__index    */
532
533     lua_pushliteral(L, \"__newindex\");
534     lua_newtable(L);                            /* for new members   */
535     lua_pushcclosure(L, &luaM_%s_newindex, 1);
536     lua_rawset(L, mt);                          /* set mt.__newindex */
537
538     lua_pushliteral(L, \"__metatable\");
539     lua_pushvalue(L, methods);                  /* dup methods table */
540     lua_rawset(L, mt);                          /* hide metatable    */
541
542     lua_setmetatable(L, methods);
543
544     lua_pop(L, 1);                              /* drop mt           */
545     return 1;                                   /* return methods    */
546 }
547
548 ")
549     in List.iter do_c_aux
550
551 (* }}} *)
552
553   let usage () =
554     output_string stderr "usage: cpkg2c (-h | -c) file.cpkg\n";
555     exit 1
556
557   let _ =
558     let warn () = print_endline "/*** THIS FILE IS AUTOGENERATED !!! ***/" in
559     if Array.length Sys.argv <= 2 then usage();
560     let file   = Sys.argv.(2) in
561     let lexbuf = L.from_channel (open_in file) in
562     let l      = (startchunk cLine file lexbuf) in
563     match Sys.argv.(1) with
564     | "-h" -> warn (); do_h l
565     | "-c" -> warn (); do_c l
566     | _    -> usage ()
567 }