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.
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.
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,
17 * Copyright © 2007 Pierre Habouzit
25 type 'a anchor = ('a * string * int)
32 output_string stderr (sprintf "%s:%d: %s\n" fpos lpos s);
35 let put_line = printf "#line %d \"%s\"\n"
37 (* @types related {{{ *)
40 { lpos: int; fpos: string;
44 ctor : string anchor option;
45 dtor : string anchor option;
48 { lpos = 0; fpos = "";
55 let types = H.create 1031
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")];
65 (* @packages related {{{ *)
67 type typinst = bool * typedef
70 { typ: typinst; mname: string; init: string }
73 { rettype: typinst list; args: (typinst * string) list;
74 fname: string; body: string }
77 { line: int; file: string; name: string;
78 members: members anchor list;
79 methods: methods anchor list;
83 let newpkg lpos fpos name static =
84 { static = static; line = lpos; file = fpos; name = name; members = []; methods = []; }
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)
90 let parse_rettype lpos fpos s =
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)
99 | s -> List.map aux (Str.split (Str.regexp "[ \t]+,[ \t]+") s)
101 let parse_args lpos fpos s =
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)
110 | s -> List.map aux (Str.split (Str.regexp "[ \t]+,[ \t]+") s)
113 (* parsing helpers {{{ *)
115 type stanza = Buf of string anchor | Pkg of package
118 let pos = lexbuf.L.lex_curr_p in
119 lexbuf.L.lex_curr_p <-
121 L.pos_lnum = pos.L.pos_lnum + 1;
122 L.pos_bol = pos.L.pos_cnum; };
126 let lnum lexbuf = lexbuf.L.lex_curr_p.L.pos_lnum
128 let (@.) buf s = B.add_string buf s; buf
129 let (@<) buf c = B.add_char buf c; buf
131 let getchunk lpos fpos buf lexbuf =
132 let res = B.contents buf in
136 let startchunk f fpos lexbuf =
137 f (lnum lexbuf) fpos (B.create 4096) lexbuf
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)
143 let find_import relpath file =
144 if Filename.is_relative file then
145 Filename.concat (Filename.dirname relpath) file
152 (* regex aliases {{{ *)
154 let to_eol = [^'\n']* '\n'
155 let ident = ['a'-'z''A'-'Z''_']['a'-'z''A'-'Z''_''0'-'9']*
158 let typdecl = sp* ("const" sp+)? ident sp*
162 (* entry point {{{ *)
164 rule cLine lpos fpos buf = parse
165 | sp* "@type" sp+ (ident as id) sp* (':' sp* (ident as id0) sp*)? '{'
167 let line = lnum lexbuf in
168 let t0 = match id0 with
173 die line fpos (sprintf "Uknown type `%s'" id0)
175 let t = H.find types id in
177 (sprintf "type `%s' already defined at %s:%d"
180 (getchunk lpos fpos buf lexbuf) :: (
181 ext_type id {t0 with lpos = line; fpos = fpos}
185 | sp* "@import" sp+ '"' ([^'"''\n']* as file) '"' sp* '\n'
187 let file = find_import fpos file in
188 let _ = startchunk cLine file
189 (L.from_channel (open_in file)) in
190 cLine lpos fpos (buf @< '\n') (nextl lexbuf)
192 | sp* ("@package"|"@static_package" as kw) sp+ (ident as pkg) sp* '{'
194 let a = getchunk lpos fpos buf lexbuf in
195 let line = lnum lexbuf in
196 a::(ext_pkg (newpkg line fpos pkg
197 (kw = "@static_package")) lexbuf)
199 | sp* '@' { die (lnum lexbuf) fpos "Syntax error" }
200 | sp* '#' as s { let buf = cPP (buf @. s) lexbuf in
201 cLine lpos fpos buf lexbuf }
202 | "" { let buf = cCode buf lexbuf in
203 cLine lpos fpos buf lexbuf }
204 | eof { [getchunk lpos fpos buf lexbuf] }
207 (* cCode block parser {{{ *)
209 and cCode buf = parse
210 | '\n' as c { let _ = nextl lexbuf in buf @< c }
211 | '"' as c { let buf = cString (buf @< c) lexbuf in
213 | "/*" as s { let buf = cComment (buf @. s) lexbuf in
215 | "//" to_eol as s { cCode (buf @. s) (nextl lexbuf) }
216 | "'\"'" as s { cCode (buf @. s) lexbuf }
217 | _ as c { cCode (buf @< c) lexbuf }
220 (* helper rules: comment, string, cPP {{{ *)
222 and cComment buf = parse
223 | "*/" as s { buf @. s }
224 | '\n' as c { cComment (buf @< c) (nextl lexbuf) }
225 | _ as c { cComment (buf @< c) lexbuf }
227 and cString buf = parse
228 | '"' as c { buf @< c }
229 | "\\\"" as s { cString (buf @. s) lexbuf }
230 | "\\\n" as s { cString (buf @. s) (nextl lexbuf) }
231 | [^'\n'] as c { cString (buf @< c) lexbuf }
234 | [^'\n']* "\\\n" as s { cPP (buf @. s) (nextl lexbuf) }
235 | to_eol as s { let _ = nextl lexbuf in buf @. s }
238 (* parse @type {{{ *)
240 and ext_type id typ = parse
242 | "//" to_eol { ext_type id typ lexbuf }
243 | '\n' { ext_type id typ (nextl lexbuf) }
244 | "/*" { let _ = cComment (B.create 1024) lexbuf in
245 ext_type id typ lexbuf }
246 | '.' (ident as member) sp* '=' sp* ([^';''\n']+ as s) ';'
249 let do_anch s = s, typ.fpos, lnum lexbuf in
251 | "ctype" -> {typ with ctype = do_anch s}
252 | "check" -> {typ with check = do_anch s}
253 | "push" -> {typ with push = do_anch s}
254 | "ctor" -> {typ with ctor = Some (do_anch s)}
255 | "dtor" -> {typ with dtor = Some (do_anch s)}
257 die (lnum lexbuf) typ.fpos
258 (sprintf "Unknown directive `%s'" member)
261 | '}' sp* ';' (sp* '\n' as s)?
262 { id @:= typ; startchunk cLine typ.fpos
263 (if s = None then lexbuf else nextl lexbuf) }
264 | "" { die (lnum lexbuf) typ.fpos "Syntax error" }
267 (* parse @package {{{ *)
269 and ext_pkg pkg = parse
271 | "//" to_eol { ext_pkg pkg lexbuf }
272 | '\n' { ext_pkg pkg (nextl lexbuf) }
273 | "/*" { let _ = cComment (B.create 1024) lexbuf in
275 | ("const" sp+ as const)? (ident as typ) sp+
276 (ident as member) sp* '=' sp* ([^';''\n']* as init)';'
278 let m = { typ = const != None,
279 type_find (lnum lexbuf) pkg.file typ;
281 init = init; }, pkg.file, lnum lexbuf in
282 ext_pkg {pkg with members = m::pkg.members} lexbuf
284 | '(' ((typdecl ',')* typdecl as ret) ')' sp*
286 '('((typdecl ident sp* ',')* typdecl ident sp* as args)')'
287 | (typdecl as ret) (ident as fname)
288 '('((typdecl ident sp* ',')* typdecl ident sp* as args)')'
290 let rettype = parse_rettype (lnum lexbuf) pkg.file ret in
291 let args = parse_args (lnum lexbuf) pkg.file args in
292 let body = ext_body pkg (B.create 1024) lexbuf in
293 let m = { rettype = rettype;
296 body = body }, pkg.file, lnum lexbuf in
297 ext_pkg {pkg with methods = m::pkg.methods} lexbuf
299 | '}' sp* ';' (sp* '\n' as s)?
300 { Pkg({pkg with members = List.rev(pkg.members)}) :: (
301 startchunk cLine pkg.file
302 (if s = None then lexbuf else nextl lexbuf)
305 | "" { die (lnum lexbuf) pkg.file "Syntax error" }
307 and ext_body pkg buf = parse
309 | "//" to_eol as s { ext_body pkg (buf @. s) lexbuf }
310 | '\n' as c { ext_body pkg (buf @< c) lexbuf }
311 | "/*" { let buf = cComment (buf @. "/*") lexbuf in
312 ext_body pkg buf lexbuf }
313 | ';' { B.contents buf }
314 | '{' as c { let buf = ext_bodycode (buf @< c) lexbuf in
315 ext_body pkg buf lexbuf }
316 | "" { die (lnum lexbuf) pkg.file "Syntax error" }
318 and ext_bodycode buf = parse
319 | '\n' as c { ext_bodycode (buf @< c) (nextl lexbuf) }
320 | '"' as c { let buf = cString (buf @< c) lexbuf in
321 ext_bodycode buf lexbuf }
322 | "/*" as s { let buf = cComment (buf @. s) lexbuf in
323 ext_bodycode buf lexbuf }
324 | "//" to_eol as s { ext_bodycode (buf @. s) (nextl lexbuf) }
325 | "'\"'" as s { ext_bodycode (buf @. s) lexbuf }
326 | '{' as c { let buf = ext_bodycode (buf @< c) lexbuf in ext_bodycode buf lexbuf }
327 | '}' as c { buf @< c }
328 | _ as c { ext_bodycode (buf @< c) lexbuf }
333 let upper = String.capitalize
335 Str.global_replace (Str.regexp_string "$L") "L" (
336 Str.global_replace (Str.regexp_string "$$") v tpl
339 let dump_struct_type begwith pkg endwith =
340 printf "%sstrict luaM_%s_t {\n" begwith pkg.name;
341 List.iter (function (m, f, l) ->
343 let ctype = t1 (snd m.typ).ctype in
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)
350 print_endline ("}" ^ endwith)
353 let do_h_aux = function
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 dump_struct_type "" pkg ";";
359 printf "\nint luaopen_%s(lua_State *L);\n\n" pkg.name;
360 printf "#endif /* MUTT_LUA_%s_H */\n" (upper pkg.name);
361 in List.iter do_h_aux
363 let do_func pkg (fn, f, l) =
364 printf "\nstatic int luaM_%s_%s(lua_State *L)\n{\n" pkg.name fn.fname;
366 List.iter (function ((const, typ), name) ->
368 let ctype = t1 typ.ctype in
370 let (c, f, l) = typ.check in
372 printf " const %s %s = %s;\n" ctype name (tplize c (string_of_int !i))
376 let (c, f, l) = typ.check in
378 printf " %s %s = %s;\n" ctype name (tplize c (string_of_int !i))
379 | Some (ctor, f, l) ->
381 let c, f, l = typ.check in
382 tplize (sprintf "\n#line %d \"%s\"\n %s" l f c)
386 printf " %s %s = %s;\n" ctype name (tplize ctor v)
392 let do_c_aux = function
393 | Buf (s, f, l) -> printf "#line %d %s\n%s" l f s
395 (* dump struct const init *)
397 dump_struct_type "static" pkg (sprintf " %s = {\n" pkg.name)
399 printf "struct luaM_%s_t %s = {\n" pkg.name pkg.name
401 List.iter (function (m, f, l) ->
403 printf " %s,\n" (if fst m.typ then m.init else "0")
407 (* dump struct init func *)
408 printf "\nstatic void %s_init(void)\n{\n" pkg.name;
409 List.iter (function (m, f, l) ->
410 if not (fst m.typ) then
411 printf "#line %d \"%s\"\n %s.%s = %s;\n"
412 l f pkg.name m.mname m.init) pkg.members;
416 printf "static int luaM_%s_index(lua_State *L)\n{\n" pkg.name;
417 printf " const char *idx = luaL_checkstring(L, 2);\n\n";
418 printf " switch (mlua_which_token(idx, -1)) {\n";
419 printf " default:\n";
420 List.iter (function (m, _, _) ->
421 printf " case LTK_%s:\n" (upper m.mname);
422 let push, f, l = (snd m.typ).push in
424 printf " %s;\n" (tplize push (sprintf "%s.%s" pkg.name m.mname));
425 printf " return 1;\n"
427 printf " lua_rawget(L, lua_upvalueindex(2));\n";
428 printf " return 1;\n";
431 (* dump __newindex *)
432 printf "static int luaM_%s_newindex(lua_State *L)\n{\n" pkg.name;
433 printf " const char *idx = luaL_checkstring(L, 2);\n\n";
434 printf " switch (mlua_which_token(idx, -1)) {\n";
435 List.iter (function (m, _, _) ->
437 printf " case LTK_%s:\n" (upper m.mname);
440 | Some (dtor, f, l) ->
442 printf " %s;\n" (tplize dtor (sprintf "%s.%s" pkg.name m.mname))
446 let (c, f, l) = t.check in
448 printf " %s.%s = %s;\n" pkg.name m.mname (tplize c "3")
449 | Some (ctor, f, l) ->
451 let c, f, l = t.check in
452 tplize (sprintf "\n#line %d \"%s\"\n %s" l f c) "3"
455 printf " %s.%s = %s;\n" pkg.name m.mname (tplize ctor v)
457 printf " return 1;\n"
459 printf " default:\n";
460 printf " return 1;\n";
464 List.iter (do_func pkg) pkg.methods;
465 printf "\nstatic const luaL_reg luaM_%s_methods[] = {\n" pkg.name;
466 List.iter (function (f, _, _) ->
467 printf " { \"%s\", luaM_%s_%s },\n" f.fname pkg.name f.fname)
469 print_string (Str.global_replace (Str.regexp "%s") pkg.name
473 int luaopen_%s(lua_State *L)
479 /* create methods table, add it the the table of globals */
480 luaL_openlib(L, \"%s\", luaM_%s_methods, 0);
481 methods = lua_gettop(L);
483 /* create metatable for %s, add it to the registry */
484 luaL_newmetatable(L, \"%s\");
487 lua_pushliteral(L, \"%s\");
488 lua_pushvalue(L, mt); /* upvalue 1 */
489 lua_pushvalue(L, methods); /* upvalue 2 */
490 lua_pushcclosure(L, &luaM_%s_index, 2);
491 lua_rawset(L, mt); /* set mt.__index */
493 lua_pushliteral(L, \"__newindex\");
494 lua_newtable(L); /* for new members */
495 lua_pushcclosure(L, &luaM_%s_newindex, 1);
496 lua_rawset(L, mt); /* set mt.__newindex */
498 lua_pushliteral(L, \"__metatable\");
499 lua_pushvalue(L, methods); /* dup methods table */
500 lua_rawset(L, mt); /* hide metatable */
502 lua_setmetatable(L, methods);
504 lua_pop(L, 1); /* drop mt */
505 return 1; /* return methods */
509 in List.iter do_c_aux
512 print_string "usage: cpkg2c (-h | -c) file.cpkg";
517 let warn () = print_endline "/*** THIS FILE IS AUTOGENERATED !!! ***/" in
518 if Array.length Sys.argv <= 2 then usage();
519 let file = Sys.argv.(2) in
520 let lexbuf = L.from_channel (open_in file) in
521 let l = (startchunk cLine file lexbuf) in
522 match Sys.argv.(1) with
523 | "-h" -> warn (); do_h l
524 | "-c" -> warn (); do_c l