a867f55830efb77765d54a8ff75b350923c627ac
[ldk-java] / genbindings.py
1 #!/usr/bin/env python3
2 import sys, re
3
4 if len(sys.argv) != 4:
5     print("USAGE: /path/to/lightning.h /path/to/bindings/output.java /path/to/bindings/output.c")
6     sys.exit(1)
7
8 with open(sys.argv[1]) as in_h, open(sys.argv[2], "w") as out_java, open(sys.argv[3], "w") as out_c:
9     var_is_arr_regex = re.compile("\(\*([A-za-z_]*)\)\[([0-9]*)\]")
10     var_ty_regex = re.compile("([A-za-z_0-9]*)(.*)")
11     def map_type(fn_arg, print_void, ret_arr_len):
12         fn_arg = fn_arg.strip()
13         if fn_arg.startswith("MUST_USE_RES "):
14             fn_arg = fn_arg[13:]
15         if fn_arg.startswith("const "):
16             fn_arg = fn_arg[6:]
17
18         c_ty = None
19         is_ptr_to_obj = None
20         if fn_arg.startswith("void"):
21             if print_void:
22                 out_java.write("void")
23                 c_ty = "void"
24             else:
25                 return (None, None, None)
26             fn_arg = fn_arg.strip("void ")
27         elif fn_arg.startswith("bool"):
28             out_java.write("boolean")
29             c_ty = "jboolean"
30             fn_arg = fn_arg.strip("bool ")
31         elif fn_arg.startswith("uint8_t"):
32             out_java.write("byte")
33             c_ty = "jbyte"
34             fn_arg = fn_arg.strip("uint8_t ")
35         elif fn_arg.startswith("uint32_t"):
36             out_java.write("int")
37             c_ty = "jint"
38             fn_arg = fn_arg.strip("uint32_t ")
39         elif fn_arg.startswith("uint64_t"):
40             out_java.write("long")
41             c_ty = "jlong"
42             fn_arg = fn_arg.strip("uint64_t ")
43         else:
44             ma = var_ty_regex.match(fn_arg)
45             out_java.write("long")
46             out_c.write("jlong")
47             is_ptr_to_obj = ma.group(1)
48             fn_arg = ma.group(2)
49         if c_ty is not None:
50             out_c.write(c_ty)
51
52         var_is_arr = var_is_arr_regex.match(fn_arg)
53         no_ptr = fn_arg.replace('*', '')
54         if var_is_arr is not None or ret_arr_len is not None:
55             out_java.write("[] ")
56             out_c.write("Array ")
57             if var_is_arr is not None:
58                 arr_name = var_is_arr.group(1)
59                 arr_len = var_is_arr.group(2)
60                 out_java.write(arr_name)
61                 out_c.write(arr_name)
62             else:
63                 arr_name = "ret"
64                 arr_len = ret_arr_len
65             assert(c_ty == "jbyte")
66             return ("unsigned char " + arr_name + "_arr[" + arr_len + "];\n" +
67                     "(*_env)->GetByteArrayRegion (_env, """ + arr_name + ", 0, " + arr_len + ", " + arr_name + "_arr);\n" +
68                     "unsigned char (*""" + arr_name + "_ref)[" + arr_len + "] = &" + arr_name + "_arr;",
69                 (c_ty + "Array " + arr_name + "_arr = (*_env)->NewByteArray(_env, 0); // XXX: len 0\n" +
70                     "(*_env)->SetByteArrayRegion(_env, " + arr_name + "_arr, 0, " + arr_len + ", *",
71                     ");\nreturn ret_arr;"),
72                 arr_name + "_ref")
73         elif no_ptr.strip() != "":
74             # If we have a parameter name, print it (noting that it may indicate its a pointer)
75             out_java.write(" " + no_ptr.strip())
76             out_c.write(" " + no_ptr.strip())
77             if is_ptr_to_obj is not None:
78                 if no_ptr == fn_arg:
79                     return (is_ptr_to_obj + " " + no_ptr.strip() + "_conv = *(" + is_ptr_to_obj + "*)" + no_ptr.strip() + ";",
80                             "XXX2", no_ptr.strip() + "_conv")
81                 else:
82                     return (is_ptr_to_obj + "* " + no_ptr.strip() + "_conv = (" + is_ptr_to_obj + "*)" + no_ptr.strip() + ";",
83                             "XXX2", no_ptr.strip() + "_conv")
84             elif no_ptr != fn_arg:
85                 return ("YYY1", "XXX3", no_ptr.strip())
86             else:
87                 return (None, "XXX4", no_ptr.strip())
88         elif not print_void:
89             # We don't have a parameter name, and want one, just call it arg
90             out_java.write(" arg")
91             out_c.write(" arg")
92             if is_ptr_to_obj is not None:
93                 return (is_ptr_to_obj + " arg_conv = *(" + is_ptr_to_obj + "*)arg;", "XXX2", "arg_conv")
94             else:
95                 return (None, "XXX6", "arg")
96         else:
97             # We don't have a parameter name, and don't want one (cause we're returning)
98             if is_ptr_to_obj is not None:
99                 if no_ptr == fn_arg:
100                     return (None, (is_ptr_to_obj + "* ret = malloc(sizeof(" + is_ptr_to_obj + "));\n*ret = ", ";\nreturn (long)ret;"), None)
101                 else:
102                     return (None, ("return (long) ", ";"), None)
103             else:
104                 return (None, None, None)
105
106     def map_fn_args(fn_args, f):
107         for idx, arg in enumerate(fn_args.split(',')):
108             if idx != 0:
109                 out_java.write(", ")
110             if arg != "void":
111                 out_c.write(", ")
112             map_type(arg, False)
113
114     def map_fn(re_match, ret_arr_len):
115         out_java.write("\t/// " + line)
116         out_java.write("\tpublic static native ")
117         out_c.write("JNIEXPORT ")
118
119         _, ret_conv, _ = map_type(re_match.group(1), True, ret_arr_len)
120         if ret_conv is not None:
121             ret_conv_pfx, ret_conv_sfx = ret_conv
122
123         out_java.write(" " + re_match.group(2) + "(")
124         out_c.write(" JNICALL " + re_match.group(2).replace('_', '_1') + "(JNIEnv * _env, jclass _b")
125
126         arg_names = []
127         for idx, arg in enumerate(re_match.group(3).split(',')):
128             if idx != 0:
129                 out_java.write(", ")
130             if arg != "void":
131                 out_c.write(", ")
132             arg_names.append(map_type(arg, False, None))
133
134         out_java.write(");\n")
135         out_c.write(") {\n")
136
137         for arg_conv, _, _ in arg_names:
138             if arg_conv is not None:
139                 out_c.write("\t" + arg_conv.replace('\n', "\n\t") + "\n");
140
141         if ret_conv is not None:
142             out_c.write("\t" + ret_conv_pfx.replace('\n', '\n\t'));
143         else:
144             out_c.write("\treturn ");
145
146         out_c.write(re_match.group(2) + "(")
147         for idx, (_, _, arg) in enumerate(arg_names):
148             if arg is not None:
149                 if idx != 0:
150                     out_c.write(", ")
151                 out_c.write(arg)
152         out_c.write(")")
153         if ret_conv is not None:
154             out_c.write(ret_conv_sfx.replace('\n', '\n\t'))
155         else:
156             out_c.write(";")
157         out_c.write("\n}\n\n")
158
159     out_java.write("""package org.ldk;
160
161 public class bindings {
162         static {
163                 System.loadLibrary(\"lightningjni\");
164         }
165
166 """)
167     out_c.write("#include \"org_ldk_bindings.h\"\n")
168     out_c.write("#include <rust_types.h>\n\n")
169     out_c.write("#include <lightning.h>\n\n")
170
171     in_block_comment = False
172     in_block_enum = False
173     cur_block_struct = None
174     in_block_union = False
175
176     fn_ptr_regex = re.compile("^extern const ([A-Za-z_0-9\* ]*) \(\*(.*)\)\((.*)\);$")
177     fn_ret_arr_regex = re.compile("(.*) \(\*(.*)\((.*)\)\)\[([0-9]*)\];$")
178     reg_fn_regex = re.compile("([A-Za-z_0-9\* ]* \*?)([a-zA-Z_0-9]*)\((.*)\);$")
179     const_val_regex = re.compile("^extern const ([A-Za-z_0-9]*) ([A-Za-z_0-9]*);$")
180
181     for line in in_h:
182         if in_block_comment:
183             #out_java.write("\t" + line)
184             if line.endswith("*/\n"):
185                 in_block_comment = False
186         elif cur_block_struct is not None:
187             cur_block_struct  = cur_block_struct + line
188             if line.startswith("} "):
189                 field_lines = []
190                 struct_lines = cur_block_struct.split("\n")
191                 for idx, struct_line in enumerate(struct_lines):
192                     if struct_line.strip().startswith("/*"):
193                         in_block_comment = True
194                     if in_block_comment:
195                         if struct_line.endswith("*/"):
196                             in_block_comment = False
197                     else:
198                         field_lines.append(struct_line)
199                 #out_java.write("".join(field_lines) + "\n")
200                 cur_block_struct = None
201         elif in_block_union:
202             if line.startswith("} "):
203                 in_block_union = False
204         elif in_block_enum:
205             if line.startswith("} "):
206                 in_block_enum = False
207         else:
208             fn_ptr = fn_ptr_regex.match(line)
209             fn_ret_arr = fn_ret_arr_regex.match(line)
210             reg_fn = reg_fn_regex.match(line)
211             const_val = const_val_regex.match(line)
212
213             if line.startswith("#include <"):
214                 pass
215             elif line.startswith("/*"):
216                 #out_java.write("\t" + line)
217                 if not line.endswith("*/\n"):
218                     in_block_comment = True
219             elif line.startswith("typedef enum "):
220                 in_block_enum = True
221             elif line.startswith("typedef struct "):
222                 cur_block_struct = line
223             elif line.startswith("typedef union "):
224                 in_block_union = True
225             elif line.startswith("typedef "):
226                 pass
227             elif fn_ptr is not None:
228                 map_fn(fn_ptr, None)
229             elif fn_ret_arr is not None:
230                 map_fn(fn_ret_arr, fn_ret_arr.group(4))
231             elif reg_fn is not None:
232                 map_fn(reg_fn, None)
233             elif const_val_regex is not None:
234                 # TODO Map const variables
235                 pass
236             else:
237                 assert(line == "\n")
238
239     out_java.write("}\n")