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