4
Use CppHeaderParser to parse some C++ headers, and generate binding code for them.
7
bindings_generator.py BASENAME HEADER1 HEADER2 ... [-- JSON]
9
BASENAME is the name used for output files (with added suffixes).
10
HEADER1 etc. are the C++ headers to parse
12
We generate the following:
14
* BASENAME.c: C bindings file, with generated C wrapper functions. You will
15
need to build this with your project, and make sure it compiles
16
properly by adding the proper #includes etc. You can also just
17
#include this file itself in one of your existing project files.
19
* BASENAME.js: JavaScript bindings file, with generated JavaScript wrapper
20
objects. This is a high-level wrapping, using native JS classes.
22
* JSON: An optional JSON object with various optional options:
24
ignored: A list of classes and class::methods not to generate code for.
27
type_processor: Text that is eval()d into a lambda that is run on
28
all arguments. For example, you can use this to
29
change all arguments of type float& to float by
30
"type_processor": "lambda t: t if t != 'float&' else 'float'"
32
export: If false, will not export all bindings in the .js file. The
33
default is to export all bindings, which allows
34
you to run something like closure compiler advanced opts on
35
the library+bindings, and the bindings will remain accessible.
37
For example, JSON can be { "ignored": "class1,class2::func" }.
39
The C bindings file is basically a tiny C wrapper around the C++ code.
40
It's only purpose is to make it easy to access the C++ code in the JS
41
bindings, and to prevent DFE from removing the code we care about. The
42
JS bindings do more serious work, creating class structures in JS and
43
linking them to the C bindings.
46
* ammo.js is currently the biggest consumer of this code. For some
47
more docs you can see that project's README,
49
https://github.com/kripken/ammo.js
51
Another project using these bindings is box2d.js,
53
https://github.com/kripken/box2d.js
55
* Functions implemented inline in classes may not be actually
56
compiled into bitcode, unless they are actually used. That may
57
confuse the bindings generator.
59
* C strings (char *) passed to functions are treated in a special way.
60
If you pass in a pointer, it is assumed to be a pointer and left as
61
is. Otherwise it must be a JS string, and we convert it to a C
62
string on the *stack*. The C string will live for the current
63
function call. If you need it for longer, you need to create a copy
66
* You should compile with -s EXPORT_BINDINGS=1 in order for the
67
autogenerated bindings functions to be exported. This is necessary
68
when compiling in asm.js mode.
71
import os, sys, glob, re
73
__rootpath__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
74
def path_from_root(*pathelems):
75
return os.path.join(__rootpath__, *pathelems)
76
sys.path += [path_from_root('')]
77
from tools.shared import *
79
# Find ply and CppHeaderParser
80
sys.path = [path_from_root('third_party', 'ply'), path_from_root('third_party', 'CppHeaderParser')] + sys.path
81
import CppHeaderParser
83
#print glob.glob(path_from_root('tests', 'bullet', 'src', 'BulletCollision', 'CollisionDispatch', '*.h'))
85
basename = sys.argv[1]
88
type_processor = lambda t: t
92
index = sys.argv.index('--')
93
json = eval(sys.argv[index+1])
94
sys.argv = sys.argv[:index]
96
if json.get('ignored'):
97
ignored = json['ignored'].split(',')
98
if json.get('type_processor'):
99
type_processor = eval(json['type_processor'])
100
if json.get('export'):
101
export = json['export']
103
print 'zz ignoring', ignored
105
# First pass - read everything
111
for header in sys.argv[2:]:
112
text += '//// ' + header + '\n'
113
text += open(header, 'r').read()
114
all_h_name = basename + '.all.h'
115
all_h = open(all_h_name, 'w')
119
parsed = CppHeaderParser.CppHeader(all_h_name)
120
print 'zz dir: ', parsed.__dict__.keys()
121
for classname, clazz in parsed.classes.items() + parsed.structs.items():
122
print 'zz see', classname, clazz, type(clazz)
123
classes[classname] = clazz
124
if type(clazz['methods']) == dict:
125
clazz['saved_methods'] = clazz['methods']
126
clazz['methods'] = clazz['methods']['public'] # CppHeaderParser doesn't have 'public' etc. in structs. so equalize to that
128
if '::' in classname:
129
assert classname.count('::') == 1
130
parents[classname.split('::')[1]] = classname.split('::')[0]
132
if hasattr(clazz, '_public_structs'): # This is a class
133
for sname, struct in clazz._public_structs.iteritems():
134
parents[sname] = classname
135
classes[classname + '::' + sname] = struct
136
struct['name'] = sname # Missing in CppHeaderParser
137
print 'zz seen struct %s in %s' % (sname, classname)
139
if 'fields' in clazz: # This is a struct
140
print 'zz add properties!'
141
clazz['properties'] = { 'public': clazz['fields'] }
142
clazz['name'] = classname
143
clazz['inherits'] = []
144
print 'zz parents: ', parents
146
def check_has_constructor(clazz):
147
for method in clazz['methods']:
148
if method['constructor'] and not method['destructor']: return True
151
for classname, clazz in parsed.classes.items() + parsed.structs.items():
152
# Various precalculations
153
print 'zz precalc', classname
154
for method in clazz['methods'][:]:
155
method['constructor'] = method['constructor'] or (method['name'] == classname) # work around cppheaderparser issue
156
print 'z constructorhmm?', method['name'], method['constructor']#, constructor, method['name'], classname
157
args = method['parameters']
159
#if method['name'] == 'addWheel': print 'qqqq', classname, method
161
# Fill in some missing stuff
162
for i in range(len(args)):
163
if args[i]['pointer'] and '*' not in args[i]['type']:
164
args[i]['type'] += '*'
165
if args[i]['reference'] and '&' not in args[i]['type']:
166
args[i]['type'] += '&'
167
args[i]['type'] = type_processor(args[i]['type'])
168
#raw = args[i]['type'].replace('&', '').replace('*', '')
171
default_param = len(args)+1
172
for i in range(len(args)):
173
if args[i].get('default'):
177
method['parameters'] = [args[:i] for i in range(default_param-1, len(args)+1)]
178
print 'zz ', classname, 'has parameters in range', range(default_param-1, len(args)+1)
180
method['returns_text'] = method['returns']
182
method['returns_text'] = method['returns_text'].replace('static', '')
184
# Implement operators
185
if '__operator__' in method['name']:
186
if 'assignment' in method['name']:
187
method['name'] = 'op_set'
188
method['operator'] = ' return *self = arg0;'
189
elif 'add' in method['name']:
190
method['name'] = 'op_add'
191
method['operator'] = ' return *self += arg0;'
192
elif 'sub' in method['name']:
193
print 'zz subsubsub ', classname, method['name'], method['parameters'][0]
194
method['name'] = 'op_sub'
195
if len(method['parameters'][0]) == 0:
196
method['operator'] = ' static %s ret; ret = -*self; return ret;' % method['returns']
198
method['operator'] = ' return *self -= arg0; // %d : %s' % (len(method['parameters'][0]), method['parameters'][0][0]['name'])
199
elif 'imul' in method['name']:
200
method['name'] = 'op_mul'
201
method['operator'] = ' return *self *= arg0;'
202
elif 'mul' in method['name']:
203
method['name'] = 'op_mul'
204
method['operator'] = ' static %s ret; ret = *self * arg0; return ret;' % method['returns']
205
elif 'div' in method['name']:
206
method['name'] = 'op_div'
207
method['operator'] = ' return *self /= arg0;'
208
elif 'getitem' in method['name']:
209
method['name'] = 'op_get'
210
method['operator'] = ' return (*self)[arg0];'
211
elif 'delete' in method['name']:
212
method['ignore'] = True
213
elif 'new' in method['name']:
214
method['ignore'] = True
215
elif 'eq' in method['name']:
216
method['name'] = 'op_comp'
217
method['operator'] = ' return arg0 == arg1;' if len(method['parameters'][0]) == 2 else ' return *self == arg0;'
219
print 'zz unknown operator:', method['name'], ', ignoring'
220
method['ignore'] = True
222
# Fill in some missing stuff
223
method['returns_text'] = method['returns_text'].replace('&', '').replace('*', '')
224
if method['returns_text'] in parents:
225
method['returns_text'] = parents[method['returns_text']] + '::' + method['returns_text']
226
if method.get('returns_const'): method['returns_text'] = 'const ' + method['returns_text']
227
if method.get('returns_pointer'):
228
while method['returns_text'].count('*') < method['returns_pointer']:
229
method['returns_text'] += '*'
230
if method.get('returns_reference'): method['returns_text'] += '&'
231
method['returns_text'] = type_processor(method['returns_text'])
233
#print 'zz %s::%s gets %s and returns %s' % (classname, method['name'], str([arg['type'] for arg in method['parameters']]), method['returns_text'])
235
# Add getters/setters for public members
236
for prop in clazz['properties']['public']:
237
if classname + '::' + prop['name'] in ignored: continue
238
if prop.get('array_dimensions'):
239
print 'zz warning: ignoring getter/setter for array', classname + '::' + prop['name']
241
type_ = prop['type'].replace('mutable ', '')#.replace(' ', '')
242
if '<' in prop['name'] or '<' in type_:
243
print 'zz warning: ignoring getter/setter for templated class', classname + '::' + prop['name']
245
reference = type_ in classes # a raw struct or class as a prop means we need to work with a ref
246
clazz['methods'].append({
248
'name': 'get_' + prop['name'],
249
'constructor': False,
252
'returns': type_.replace(' *', '').replace('*', ''),
253
'returns_text': type_ + ('&' if reference else ''),
254
'returns_reference': reference,
255
'returns_pointer': '*' in type_,
256
'pure_virtual': False,
259
clazz['methods'].append({
261
'name': 'set_' + prop['name'],
262
'constructor': False,
266
'returns_text': 'void',
267
'returns_reference': False,
268
'returns_pointer': False,
269
'pure_virtual': False,
271
'type': type_ + ('&' if reference else ''),
276
print 'zz is effectively abstract?', clazz['name'], classname, '0'
277
if 'saved_methods' in clazz and not check_has_constructor(clazz):
278
print 'zz is effectively abstract?', clazz['name'], '1'
279
# Having a private constructor and no public constructor means you are, in effect, abstract
280
for private_method in clazz['saved_methods']['private']:
281
print 'zz is effectively abstract?', clazz['name'], '2'
282
if private_method['constructor']:
283
print 'zz is effectively abstract?', clazz['name'], '3'
284
clazz['effectively_abstract'] = True
287
if not clazz.get('abstract') and not clazz.get('effectively_abstract'):
288
clazz['methods'].append({
290
'name': '__destroy__',
291
'constructor': False,
295
'returns_text': 'void',
296
'returns_reference': False,
297
'returns_pointer': False,
298
'pure_virtual': False,
302
clazz['methods'] = filter(lambda method: not method.get('ignore'), clazz['methods'])
304
# Explore all functions we need to generate, including parent classes, handling of overloading, etc.
307
return t.replace('const ', '').replace('struct ', '').replace('&', '').replace('*', '').replace(' ', '')
309
def fix_template_value(t): # Not sure why this is needed, might be a bug in CppHeaderParser
310
if t == 'unsignedshortint':
311
return 'unsigned short int'
312
elif t == 'unsignedint':
313
return 'unsigned int'
323
ret.append(copiedarg)
326
for classname, clazz in parsed.classes.items() + parsed.structs.items():
327
clazz['final_methods'] = {}
329
def explore(subclass, template_name=None, template_value=None):
330
# Do our functions first, and do not let later classes override
331
for method in subclass['methods']:
332
print classname, 'exploring', subclass['name'], '::', method['name']
334
if method['constructor']:
335
if clazz != subclass:
336
print "zz Subclasses cannot directly use their parent's constructors"
338
if method['destructor']:
339
print 'zz Nothing to do there'
342
if method.get('operator') and subclass is not clazz:
343
print 'zz Do not use parent class operators. Cast to that class if you need those operators (castObject)'
346
if method['name'] not in clazz['final_methods']:
347
copied = clazz['final_methods'][method['name']] = {}
348
for key in ['name', 'constructor', 'static', 'returns', 'returns_text', 'returns_reference', 'returns_pointer', 'destructor', 'pure_virtual',
349
'getter', 'setter', 'destroyer', 'operator']:
350
copied[key] = method.get(key)
351
copied['origin'] = subclass
352
copied['parameters'] = [];
353
for args in method['parameters']:
354
# Copy the arguments, since templating may cause them to be altered
355
copied['parameters'].append(copy_args(args))
357
# Set template values
358
copied['returns'] = copied['returns'].replace(template_name, template_value)
359
copied['returns_text'] = copied['returns_text'].replace(template_name, template_value)
360
for args in copied['parameters']:
362
arg['type'] = arg['type'].replace(template_name, template_value)
364
# Merge the new function in the best way we can. Two signatures (args) must differ in their number
366
if method.get('operator'): continue # do not merge operators
368
curr = clazz['final_methods'][method['name']]
370
if curr['origin'] is not subclass: continue # child class functions mask/hide parent functions of the same name in C++
373
for curr_args in curr['parameters']:
374
for method_args in method['parameters']:
375
if len(curr_args) == len(method_args):
378
print 'Warning: Cannot mix in overloaded functions', method['name'], 'in class', classname, ', skipping'
380
# TODO: Other compatibility checks, if any?
382
curr['parameters'] += map(copy_args, method['parameters'])
384
print 'zz ', classname, 'has updated parameters of ', curr['parameters']
387
if subclass.get('inherits'):
388
for parent in subclass['inherits']:
389
parent = parent['class']
391
template_value = None
393
parent, template = parent.split('<')
394
template_name = classes[parent]['template_typename']
395
template_value = fix_template_value(template.replace('>', ''))
396
print 'template', template_value, 'for', classname, '::', parent, ' | ', template_name
397
if parent not in classes and '::' in classname: # They might both be subclasses in the same parent
398
parent = classname.split('::')[0] + '::' + parent
399
if parent not in classes:
400
print 'Warning: parent class', parent, 'not a known class. Ignoring.'
402
explore(classes[parent], template_name, template_value)
406
for method in clazz['final_methods'].itervalues():
407
method['parameters'].sort(key=len)
409
# Second pass - generate bindings
410
# TODO: Bind virtual functions using dynamic binding in the C binding code
412
funcs = {} # name -> # of copies in the original, and originalname in a copy
414
gen_c = open(basename + '.cpp', 'w')
415
gen_js = open(basename + '.js', 'w')
417
gen_c.write('extern "C" {\n')
420
// Bindings utilities
422
var Object__cache = {}; // we do it this way so we do not modify |Object|
423
function wrapPointer(ptr, __class__) {
424
var cache = __class__ ? __class__.prototype.__cache__ : Object__cache;
425
var ret = cache[ptr];
427
__class__ = __class__ || Object;
428
ret = Object.create(__class__.prototype);
430
ret.__class__ = __class__;
431
return cache[ptr] = ret;
433
Module['wrapPointer'] = wrapPointer;
435
function castObject(obj, __class__) {
436
return wrapPointer(obj.ptr, __class__);
438
Module['castObject'] = castObject;
440
Module['NULL'] = wrapPointer(0);
442
function destroy(obj) {
443
if (!obj['__destroy__']) throw 'Error: Cannot destroy object. (Did you create it yourself?)';
444
obj['__destroy__']();
445
// Remove from cache, so the object can be GC'd and refs added onto it released
446
if (obj.__class__ !== Object) {
447
delete obj.__class__.prototype.__cache__[obj.ptr];
449
delete Object__cache[obj.ptr];
452
Module['destroy'] = destroy;
454
function compare(obj1, obj2) {
455
return obj1.ptr === obj2.ptr;
457
Module['compare'] = compare;
459
function getPointer(obj) {
462
Module['getPointer'] = getPointer;
464
function getClass(obj) {
465
return obj.__class__;
467
Module['getClass'] = getClass;
469
function customizeVTable(object, replacementPairs) {
470
// Does not handle multiple inheritance
471
// Does not work with asm.js
473
// Find out vtable size
474
var vTable = getValue(object.ptr, 'void*');
475
// This assumes our modification where we null-terminate vtables
477
while (getValue(vTable + Runtime.QUANTUM_SIZE*size, 'void*')) {
481
// Prepare replacement lookup table and add replacements to FUNCTION_TABLE
482
// There is actually no good way to do this! So we do the following hack:
483
// We create a fake vtable with canary functions, to detect which actual
484
// function is being called
485
var vTable2 = _malloc(size*Runtime.QUANTUM_SIZE);
486
setValue(object.ptr, vTable2, 'void*');
488
var functions = FUNCTION_TABLE.length;
489
for (var i = 0; i < size; i++) {
490
var index = FUNCTION_TABLE.length;
492
FUNCTION_TABLE.push(function() {
496
FUNCTION_TABLE.push(0);
497
setValue(vTable2 + Runtime.QUANTUM_SIZE*i, index, 'void*');
499
var args = [{ptr: 0}];
500
replacementPairs.forEach(function(pair) {
501
// We need the wrapper function that converts arguments to not fail. Keep adding arguments til it works.
504
pair['original'].apply(object, args);
510
pair.originalIndex = getValue(vTable + canaryValue*Runtime.QUANTUM_SIZE, 'void*');
512
FUNCTION_TABLE = FUNCTION_TABLE.slice(0, functions);
514
// Do the replacements
516
var replacements = {};
517
replacementPairs.forEach(function(pair) {
518
var replacementIndex = FUNCTION_TABLE.length;
519
FUNCTION_TABLE.push(pair['replacement']);
520
FUNCTION_TABLE.push(0);
521
replacements[pair.originalIndex] = replacementIndex;
524
// Copy and modify vtable
525
for (var i = 0; i < size; i++) {
526
var value = getValue(vTable + Runtime.QUANTUM_SIZE*i, 'void*');
527
if (value in replacements) value = replacements[value];
528
setValue(vTable2 + Runtime.QUANTUM_SIZE*i, value, 'void*');
532
Module['customizeVTable'] = customizeVTable;
534
// Converts a value into a C-style string.
535
function ensureString(value) {
536
if (typeof value == 'number') return value;
537
return allocate(intArrayFromString(value), 'i8', ALLOC_STACK);
541
def generate_wrapping_code(classname):
542
return '''%(classname)s.prototype.__cache__ = {};
543
''' % { 'classname': classname }
544
# %(classname)s.prototype['fields'] = Runtime.generateStructInfo(null, '%(classname)s'); - consider adding this
546
def generate_class(generating_classname, classname, clazz): # TODO: deprecate generating?
547
print 'zz generating:', generating_classname, classname
548
generating_classname_head = generating_classname.split('::')[-1]
549
classname_head = classname.split('::')[-1]
551
inherited = generating_classname_head != classname_head
553
abstract = clazz['abstract']
555
# For abstract base classes, add a function definition on top. There is no constructor
556
gen_js.write('\nfunction ' + generating_classname_head + ('(){ throw "%s is abstract!" }\n' % generating_classname_head) + generate_wrapping_code(generating_classname_head))
558
gen_js.write('''Module['%s'] = %s;
559
''' % (generating_classname_head, generating_classname_head))
561
print 'zz methods: ', clazz['final_methods'].keys()
562
for method in clazz['final_methods'].itervalues():
563
mname = method['name']
564
if classname_head + '::' + mname in ignored:
565
print 'zz ignoring', mname
568
params = method['parameters']
569
constructor = method['constructor']
570
destructor = method['destructor']
571
static = method['static']
573
print 'zz generating %s::%s' % (generating_classname, method['name'])
575
if destructor: continue
576
if constructor and inherited: continue
577
if constructor and clazz['abstract']: continue # do not generate constructors for abstract base classes
580
for i in range(len(args)):
581
#print 'zz arggggggg', classname, 'x', mname, 'x', args[i]['name'], 'x', args[i]['type'], 'x', dir(args[i]), 'y', args[i].get('default'), 'z', args[i].get('defaltValue'), args[i].keys()
583
if args[i]['name'].replace(' ', '') == '':
584
args[i]['name'] = 'arg' + str(i+1)
585
elif args[i]['name'] == '&':
586
args[i]['name'] = 'arg' + str(i+1)
587
args[i]['type'] += '&'
589
#print 'c1', parents.keys()
590
if args[i]['type'][-1] == '&':
591
sname = args[i]['type'][:-1]
592
if sname[-1] == ' ': sname = sname[:-1]
594
args[i]['type'] = parents[sname] + '::' + sname + '&'
595
elif sname.replace('const ', '') in parents:
596
sname = sname.replace('const ', '')
597
args[i]['type'] = 'const ' + parents[sname] + '::' + sname + '&'
598
#print 'POST arggggggg', classname, 'x', mname, 'x', args[i]['name'], 'x', args[i]['type']
600
ret = ((classname + ' *') if constructor else method['returns_text'])#.replace('virtual ', '')
601
has_return = ret.replace(' ', '') != 'void'
602
callprefix = 'new ' if constructor else ('self->' if not static else (classname + '::'))
605
actualmname = classname if constructor else (method.get('truename') or mname)
606
if method.get('getter') or method.get('setter'):
607
actualmname = actualmname[4:]
609
need_self = not constructor and not static
612
return ([] if not need_self else [classname + ' * self']) + map(lambda i: args[i]['type'] + ' arg' + str(i), range(len(args)))
614
return map(lambda i: 'arg' + str(i), range(len(args)))
615
def justtypes(args): # note: this ignores 'self'
616
return map(lambda i: args[i]['type'], range(len(args)))
618
return ([] if not need_self else ['(%s*)argv[argc]' % classname]) + map(lambda i: '(%s)argv[argc]' % args[i]['type'], range(len(args)))
620
fullname = ('emscripten_bind_' + generating_classname + '__' + mname).replace('::', '__')
621
generating_classname_suffixed = generating_classname
622
mname_suffixed = mname
623
count = funcs.setdefault(fullname, 0)
630
suffix = '_' + str(count+1)
631
funcs[fullname + suffix] = 0
633
mname_suffixed += suffix
635
generating_classname_suffixed += suffix
641
# If we are returning a *copy* of an object, we return instead to a ref of a static held here. This seems the best compromise
642
staticize = not constructor and ret.replace(' ', '') != 'void' and method['returns'] in classes and (not method['returns_reference'] and not method['returns_pointer'])
643
# Make sure to mark our bindings wrappers in a way that they will not be inlined, eliminated as unneeded, or optimized into other signatures
645
%s __attribute__((used, noinline)) %s_p%d(%s) {''' % (ret if not staticize else (ret + '&'), fullname, i,
646
', '.join(typedargs(args)[:i + (0 if not need_self else 1)])))
649
if method.get('getter'):
650
gen_c.write(''' return self->%s;''' % actualmname)
651
elif method.get('setter'):
652
gen_c.write(''' self->%s = arg0;''' % actualmname)
653
elif method.get('destroyer'):
654
gen_c.write(''' delete self;''')
655
elif method.get('operator'):
656
gen_c.write(method['operator'])
657
else: # normal method
658
gen_c.write(''' %s%s%s(%s);''' % ('return ' if has_return else '',
659
callprefix, actualmname, ', '.join(justargs(args)[:i])))
664
if method.get('operator'):
665
gen_c.write(method['operator'])
667
gen_c.write(''' static %s ret; ret = %s%s(%s);
668
return ret;''' % (method['returns'],
669
callprefix, actualmname,
670
', '.join(justargs(args)[:i])))
675
#print 'zz types:', map(lambda arg: arg['type'], args)
677
has_string_convs = False
681
#print 'js loopin', params, '|', len(args)#, args
683
# We can assume that NULL is passed for null pointers, so object arguments can always
684
# have .ptr done on them
685
justargs_fixed = justargs(args)[:]
686
for i in range(len(args)):
688
clean = clean_type(arg['type'])
690
justargs_fixed[i] += '.ptr'
691
elif arg['type'].replace(' ', '').endswith('char*'):
692
justargs_fixed[i] = 'ensureString(' + justargs_fixed[i] + ')'
693
has_string_convs = True
696
if args != params[0]:
698
if args != params[-1]:
699
calls += ' if (arg' + str(i) + ' === undefined)'
700
calls += '\n ' + (' ' if len(params) > 0 else '')
703
calls += '''this.ptr = _%s_p%d(%s);
704
''' % (fullname, i, ', '.join(justargs_fixed[:i]))
706
calls += '''this.ptr = _%s_p%d(%s);
707
''' % (fullname, i, ', '.join(justargs_fixed[:i]))
709
return_value = '''_%s_p%d(%s)''' % (fullname, i, ', '.join((['this.ptr'] if need_self else []) + justargs_fixed[:i]))
710
print 'zz making return', classname, method['name'], method['returns'], return_value
711
if method['returns'] in classes:
713
calls += '''return wrapPointer(%s, Module['%s']);''' % (return_value, method['returns'].split('::')[-1])
716
calls += ('return ' if ret != 'void' else '') + return_value + ';'
720
calls = 'var stack = Runtime.stackSave();\ntry {\n' + calls + '} finally { Runtime.stackRestore(stack) }\n';
722
print 'Maekin:', classname, generating_classname, mname, mname_suffixed
725
%s.prototype.__cache__[this.ptr] = this;
726
this.__class__ = %s;''' % (mname_suffixed, mname_suffixed)
732
%s''' % (mname_suffixed, ', '.join(justargs(args)), calls, generate_wrapping_code(generating_classname_head))
738
%s.prototype = %s.prototype;
739
''' % (mname_suffixed, ', '.join(justargs(args)), calls, mname_suffixed, classname)
744
''' % (mname_suffixed, mname_suffixed)
748
%s.prototype%s = function(%s) {
751
''' % (generating_classname_head, ('.' + mname_suffixed) if not export else ("['" + mname_suffixed + "']"), ', '.join(justargs(args)), calls)
753
js_text = js_text.replace('\n\n', '\n').replace('\n\n', '\n')
754
gen_js.write(js_text)
758
for classname, clazz in parsed.classes.items() + parsed.structs.items():
759
if any([name in ignored for name in classname.split('::')]):
760
print 'zz ignoring', classname
763
if clazz.get('template_typename'):
764
print 'zz ignoring templated base class', classname
767
# Nothing to generate for pure virtual classes XXX actually this is not so. We do need to generate wrappers for returned objects,
768
# they are of a concrete class of course, but an known one, so we create a wrapper for an abstract base class.
770
possible_prefix = (classname.split('::')[0] + '::') if '::' in classname else ''
772
def check_pure_virtual(clazz, progeny):
773
#if not clazz.get('inherits'): return False # If no inheritance info, not a class, this is a CppHeaderParser struct
774
print 'Checking pure virtual for', clazz['name'], clazz['inherits']
775
# If we do not recognize any of the parent classes, assume this is pure virtual - ignore it
776
parents = [parent['class'] for parent in clazz['inherits']]
777
parents = [parent.split('<')[0] for parent in parents] # remove template stuff
778
parents = [parent if parent in classes else possible_prefix + parent for parent in parents]
779
if any([not parent in classes for parent in parents]):
780
print 'zz Warning: unknown parent class', parents, 'for', classname
782
if any([check_pure_virtual(classes[parent], [clazz] + progeny) for parent in parents]): return True
785
#print 'zz checking dirtiness for', mname, 'in', progeny
786
for progen in progeny:
787
for method in progen['methods']:
788
if method['name'] == mname and not method['pure_virtual']:
791
#print 'zz not dirtied'
794
for method in clazz['methods']:
795
if method['pure_virtual'] and not dirtied(method['name']):
796
print 'zz ignoring pure virtual class', classname, 'due to', method['name']
799
clazz['abstract'] = check_pure_virtual(clazz, []) or clazz.get('effectively_abstract')
801
print 'zz', classname, 'is abstract?', clazz['abstract']
802
#if check_pure_virtual(clazz, []):
805
# Add a constructor if none exist
806
has_constructor = check_has_constructor(clazz)
808
print 'zz', classname, 'has constructor?', has_constructor
810
if not has_constructor:
811
if not clazz['abstract']:
812
print 'zz no constructor for', classname, 'and not abstract, so ignoring'
815
#clazz['methods'] = [{
818
# 'pure_virtual': False,
819
# 'destructor': False,
820
#}] + clazz['methods']
822
generate_class(classname, classname, clazz)
824
# TODO: Add a destructor