blob: 5bf0996ed94494334252eaae954e896b6d88a613 [file] [log] [blame]
#!/usr/bin/env python2
'''
Use CppHeaderParser to parse some C++ headers, and generate binding code for them.
Usage:
bindings_generator.py BASENAME HEADER1 HEADER2 ... [-- JSON]
BASENAME is the name used for output files (with added suffixes).
HEADER1 etc. are the C++ headers to parse
We generate the following:
* BASENAME.c: C bindings file, with generated C wrapper functions. You will
need to build this with your project, and make sure it compiles
properly by adding the proper #includes etc. You can also just
#include this file itself in one of your existing project files.
* BASENAME.js: JavaScript bindings file, with generated JavaScript wrapper
objects. This is a high-level wrapping, using native JS classes.
* JSON: An optional JSON object with various optional options:
ignored: A list of classes and class::methods not to generate code for.
Comma separated.
type_processor: Text that is eval()d into a lambda that is run on
all arguments. For example, you can use this to
change all arguments of type float& to float by
"type_processor": "lambda t: t if t != 'float&' else 'float'"
export: If false, will not export all bindings in the .js file. The
default is to export all bindings, which allows
you to run something like closure compiler advanced opts on
the library+bindings, and the bindings will remain accessible.
For example, JSON can be { "ignored": "class1,class2::func" }.
The C bindings file is basically a tiny C wrapper around the C++ code.
It's only purpose is to make it easy to access the C++ code in the JS
bindings, and to prevent DFE from removing the code we care about. The
JS bindings do more serious work, creating class structures in JS and
linking them to the C bindings.
Notes:
* ammo.js is currently the biggest consumer of this code. For some
more docs you can see that project's README,
https://github.com/kripken/ammo.js
Another project using these bindings is box2d.js,
https://github.com/kripken/box2d.js
* Functions implemented inline in classes may not be actually
compiled into bitcode, unless they are actually used. That may
confuse the bindings generator.
* C strings (char *) passed to functions are treated in a special way.
If you pass in a pointer, it is assumed to be a pointer and left as
is. Otherwise it must be a JS string, and we convert it to a C
string on the *stack*. The C string will live for the current
function call. If you need it for longer, you need to create a copy
in your C++ code.
* You should compile with -s EXPORT_BINDINGS=1 in order for the
autogenerated bindings functions to be exported. This is necessary
when compiling in asm.js mode.
'''
import os, sys, glob, re
__rootpath__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def path_from_root(*pathelems):
return os.path.join(__rootpath__, *pathelems)
sys.path += [path_from_root('')]
from tools.shared import *
# Find ply and CppHeaderParser
sys.path = [path_from_root('third_party', 'ply'), path_from_root('third_party', 'CppHeaderParser')] + sys.path
import CppHeaderParser
#print glob.glob(path_from_root('tests', 'bullet', 'src', 'BulletCollision', 'CollisionDispatch', '*.h'))
basename = sys.argv[1]
ignored = []
type_processor = lambda t: t
export = 1
if '--' in sys.argv:
index = sys.argv.index('--')
json = eval(sys.argv[index+1])
sys.argv = sys.argv[:index]
if json.get('ignored'):
ignored = json['ignored'].split(',')
if json.get('type_processor'):
type_processor = eval(json['type_processor'])
if json.get('export'):
export = json['export']
print 'zz ignoring', ignored
# First pass - read everything
classes = {}
parents = {}
text = ''
for header in sys.argv[2:]:
text += '//// ' + header + '\n'
text += open(header, 'r').read()
all_h_name = basename + '.all.h'
all_h = open(all_h_name, 'w')
all_h.write(text)
all_h.close()
parsed = CppHeaderParser.CppHeader(all_h_name)
print 'zz dir: ', parsed.__dict__.keys()
for classname, clazz in parsed.classes.items() + parsed.structs.items():
print 'zz see', classname, clazz, type(clazz)
classes[classname] = clazz
if type(clazz['methods']) == dict:
clazz['saved_methods'] = clazz['methods']
clazz['methods'] = clazz['methods']['public'] # CppHeaderParser doesn't have 'public' etc. in structs. so equalize to that
if '::' in classname:
assert classname.count('::') == 1
parents[classname.split('::')[1]] = classname.split('::')[0]
if hasattr(clazz, '_public_structs'): # This is a class
for sname, struct in clazz._public_structs.iteritems():
parents[sname] = classname
classes[classname + '::' + sname] = struct
struct['name'] = sname # Missing in CppHeaderParser
print 'zz seen struct %s in %s' % (sname, classname)
if 'fields' in clazz: # This is a struct
print 'zz add properties!'
clazz['properties'] = { 'public': clazz['fields'] }
clazz['name'] = classname
clazz['inherits'] = []
print 'zz parents: ', parents
def check_has_constructor(clazz):
for method in clazz['methods']:
if method['constructor'] and not method['destructor']: return True
return False
for classname, clazz in parsed.classes.items() + parsed.structs.items():
# Various precalculations
print 'zz precalc', classname
for method in clazz['methods'][:]:
method['constructor'] = method['constructor'] or (method['name'] == classname) # work around cppheaderparser issue
print 'z constructorhmm?', method['name'], method['constructor']#, constructor, method['name'], classname
args = method['parameters']
#if method['name'] == 'addWheel': print 'qqqq', classname, method
# Fill in some missing stuff
for i in range(len(args)):
if args[i]['pointer'] and '*' not in args[i]['type']:
args[i]['type'] += '*'
if args[i]['reference'] and '&' not in args[i]['type']:
args[i]['type'] += '&'
args[i]['type'] = type_processor(args[i]['type'])
#raw = args[i]['type'].replace('&', '').replace('*', '')
#if raw in classes:
default_param = len(args)+1
for i in range(len(args)):
if args[i].get('default'):
default_param = i+1
break
method['parameters'] = [args[:i] for i in range(default_param-1, len(args)+1)]
print 'zz ', classname, 'has parameters in range', range(default_param-1, len(args)+1)
method['returns_text'] = method['returns']
if method['static']:
method['returns_text'] = method['returns_text'].replace('static', '')
# Implement operators
if '__operator__' in method['name']:
if 'assignment' in method['name']:
method['name'] = 'op_set'
method['operator'] = ' return *self = arg0;'
elif 'add' in method['name']:
method['name'] = 'op_add'
method['operator'] = ' return *self += arg0;'
elif 'sub' in method['name']:
print 'zz subsubsub ', classname, method['name'], method['parameters'][0]
method['name'] = 'op_sub'
if len(method['parameters'][0]) == 0:
method['operator'] = ' static %s ret; ret = -*self; return ret;' % method['returns']
else:
method['operator'] = ' return *self -= arg0; // %d : %s' % (len(method['parameters'][0]), method['parameters'][0][0]['name'])
elif 'imul' in method['name']:
method['name'] = 'op_mul'
method['operator'] = ' return *self *= arg0;'
elif 'mul' in method['name']:
method['name'] = 'op_mul'
method['operator'] = ' static %s ret; ret = *self * arg0; return ret;' % method['returns']
elif 'div' in method['name']:
method['name'] = 'op_div'
method['operator'] = ' return *self /= arg0;'
elif 'getitem' in method['name']:
method['name'] = 'op_get'
method['operator'] = ' return (*self)[arg0];'
elif 'delete' in method['name']:
method['ignore'] = True
elif 'new' in method['name']:
method['ignore'] = True
elif 'eq' in method['name']:
method['name'] = 'op_comp'
method['operator'] = ' return arg0 == arg1;' if len(method['parameters'][0]) == 2 else ' return *self == arg0;'
else:
print 'zz unknown operator:', method['name'], ', ignoring'
method['ignore'] = True
# Fill in some missing stuff
method['returns_text'] = method['returns_text'].replace('&', '').replace('*', '')
if method['returns_text'] in parents:
method['returns_text'] = parents[method['returns_text']] + '::' + method['returns_text']
if method.get('returns_const'): method['returns_text'] = 'const ' + method['returns_text']
if method.get('returns_pointer'):
while method['returns_text'].count('*') < method['returns_pointer']:
method['returns_text'] += '*'
if method.get('returns_reference'): method['returns_text'] += '&'
method['returns_text'] = type_processor(method['returns_text'])
#print 'zz %s::%s gets %s and returns %s' % (classname, method['name'], str([arg['type'] for arg in method['parameters']]), method['returns_text'])
# Add getters/setters for public members
for prop in clazz['properties']['public']:
if classname + '::' + prop['name'] in ignored: continue
if prop.get('array_dimensions'):
print 'zz warning: ignoring getter/setter for array', classname + '::' + prop['name']
continue
type_ = prop['type'].replace('mutable ', '')#.replace(' ', '')
if '<' in prop['name'] or '<' in type_:
print 'zz warning: ignoring getter/setter for templated class', classname + '::' + prop['name']
continue
reference = type_ in classes # a raw struct or class as a prop means we need to work with a ref
clazz['methods'].append({
'getter': True,
'name': 'get_' + prop['name'],
'constructor': False,
'destructor': False,
'static': False,
'returns': type_.replace(' *', '').replace('*', ''),
'returns_text': type_ + ('&' if reference else ''),
'returns_reference': reference,
'returns_pointer': '*' in type_,
'pure_virtual': False,
'parameters': [[]],
})
clazz['methods'].append({
'setter': True,
'name': 'set_' + prop['name'],
'constructor': False,
'destructor': False,
'static': False,
'returns': 'void',
'returns_text': 'void',
'returns_reference': False,
'returns_pointer': False,
'pure_virtual': False,
'parameters': [[{
'type': type_ + ('&' if reference else ''),
'name': 'value',
}]],
})
print 'zz is effectively abstract?', clazz['name'], classname, '0'
if 'saved_methods' in clazz and not check_has_constructor(clazz):
print 'zz is effectively abstract?', clazz['name'], '1'
# Having a private constructor and no public constructor means you are, in effect, abstract
for private_method in clazz['saved_methods']['private']:
print 'zz is effectively abstract?', clazz['name'], '2'
if private_method['constructor']:
print 'zz is effectively abstract?', clazz['name'], '3'
clazz['effectively_abstract'] = True
# Add destroyer
if not clazz.get('abstract') and not clazz.get('effectively_abstract'):
clazz['methods'].append({
'destroyer': True,
'name': '__destroy__',
'constructor': False,
'destructor': False,
'static': False,
'returns': 'void',
'returns_text': 'void',
'returns_reference': False,
'returns_pointer': False,
'pure_virtual': False,
'parameters': [[]],
})
clazz['methods'] = filter(lambda method: not method.get('ignore'), clazz['methods'])
# Explore all functions we need to generate, including parent classes, handling of overloading, etc.
def clean_type(t):
return t.replace('const ', '').replace('struct ', '').replace('&', '').replace('*', '').replace(' ', '')
def fix_template_value(t): # Not sure why this is needed, might be a bug in CppHeaderParser
if t == 'unsignedshortint':
return 'unsigned short int'
elif t == 'unsignedint':
return 'unsigned int'
return t
def copy_args(args):
ret = []
for arg in args:
copiedarg = {
'type': arg['type'],
'name': arg['name'],
}
ret.append(copiedarg)
return ret
for classname, clazz in parsed.classes.items() + parsed.structs.items():
clazz['final_methods'] = {}
def explore(subclass, template_name=None, template_value=None):
# Do our functions first, and do not let later classes override
for method in subclass['methods']:
print classname, 'exploring', subclass['name'], '::', method['name']
if method['constructor']:
if clazz != subclass:
print "zz Subclasses cannot directly use their parent's constructors"
continue
if method['destructor']:
print 'zz Nothing to do there'
continue
if method.get('operator') and subclass is not clazz:
print 'zz Do not use parent class operators. Cast to that class if you need those operators (castObject)'
continue
if method['name'] not in clazz['final_methods']:
copied = clazz['final_methods'][method['name']] = {}
for key in ['name', 'constructor', 'static', 'returns', 'returns_text', 'returns_reference', 'returns_pointer', 'destructor', 'pure_virtual',
'getter', 'setter', 'destroyer', 'operator']:
copied[key] = method.get(key)
copied['origin'] = subclass
copied['parameters'] = [];
for args in method['parameters']:
# Copy the arguments, since templating may cause them to be altered
copied['parameters'].append(copy_args(args))
if template_name:
# Set template values
copied['returns'] = copied['returns'].replace(template_name, template_value)
copied['returns_text'] = copied['returns_text'].replace(template_name, template_value)
for args in copied['parameters']:
for arg in args:
arg['type'] = arg['type'].replace(template_name, template_value)
else:
# Merge the new function in the best way we can. Two signatures (args) must differ in their number
if method.get('operator'): continue # do not merge operators
curr = clazz['final_methods'][method['name']]
if curr['origin'] is not subclass: continue # child class functions mask/hide parent functions of the same name in C++
problem = False
for curr_args in curr['parameters']:
for method_args in method['parameters']:
if len(curr_args) == len(method_args):
problem = True
if problem:
print 'Warning: Cannot mix in overloaded functions', method['name'], 'in class', classname, ', skipping'
continue
# TODO: Other compatibility checks, if any?
curr['parameters'] += map(copy_args, method['parameters'])
print 'zz ', classname, 'has updated parameters of ', curr['parameters']
# Recurse
if subclass.get('inherits'):
for parent in subclass['inherits']:
parent = parent['class']
template_name = None
template_value = None
if '<' in parent:
parent, template = parent.split('<')
template_name = classes[parent]['template_typename']
template_value = fix_template_value(template.replace('>', ''))
print 'template', template_value, 'for', classname, '::', parent, ' | ', template_name
if parent not in classes and '::' in classname: # They might both be subclasses in the same parent
parent = classname.split('::')[0] + '::' + parent
if parent not in classes:
print 'Warning: parent class', parent, 'not a known class. Ignoring.'
return
explore(classes[parent], template_name, template_value)
explore(clazz)
for method in clazz['final_methods'].itervalues():
method['parameters'].sort(key=len)
# Second pass - generate bindings
# TODO: Bind virtual functions using dynamic binding in the C binding code
funcs = {} # name -> # of copies in the original, and originalname in a copy
gen_c = open(basename + '.cpp', 'w')
gen_js = open(basename + '.js', 'w')
gen_c.write('extern "C" {\n')
gen_js.write('''
// Bindings utilities
var Object__cache = {}; // we do it this way so we do not modify |Object|
function wrapPointer(ptr, __class__) {
var cache = __class__ ? __class__.prototype.__cache__ : Object__cache;
var ret = cache[ptr];
if (ret) return ret;
__class__ = __class__ || Object;
ret = Object.create(__class__.prototype);
ret.ptr = ptr;
ret.__class__ = __class__;
return cache[ptr] = ret;
}
Module['wrapPointer'] = wrapPointer;
function castObject(obj, __class__) {
return wrapPointer(obj.ptr, __class__);
}
Module['castObject'] = castObject;
Module['NULL'] = wrapPointer(0);
function destroy(obj) {
if (!obj['__destroy__']) throw 'Error: Cannot destroy object. (Did you create it yourself?)';
obj['__destroy__']();
// Remove from cache, so the object can be GC'd and refs added onto it released
if (obj.__class__ !== Object) {
delete obj.__class__.prototype.__cache__[obj.ptr];
} else {
delete Object__cache[obj.ptr];
}
}
Module['destroy'] = destroy;
function compare(obj1, obj2) {
return obj1.ptr === obj2.ptr;
}
Module['compare'] = compare;
function getPointer(obj) {
return obj.ptr;
}
Module['getPointer'] = getPointer;
function getClass(obj) {
return obj.__class__;
}
Module['getClass'] = getClass;
function customizeVTable(object, replacementPairs) {
// Does not handle multiple inheritance
// Find out vtable size
var vTable = getValue(object.ptr, 'void*');
// This assumes our modification where we null-terminate vtables
var size = 0;
while (getValue(vTable + Runtime.QUANTUM_SIZE*size, 'void*')) {
size++;
}
// Prepare replacement lookup table and add replacements.
// There is actually no good way to do this! So we do the following hack:
// We create a fake vtable with canary functions, to detect which actual
// function is being called
var vTable2 = _malloc(size*Runtime.QUANTUM_SIZE);
setValue(object.ptr, vTable2, 'void*');
var canaryValue;
var tempFuncs = [];
for (var i = 0; i < size; i++) {
(function(j) {
var index = Runtime.addFunction(function() {
canaryValue = j;
});
setValue(vTable2 + Runtime.QUANTUM_SIZE*i, index, 'void*');
tempFuncs.push(index);
})(i);
}
var args = [{ptr: 0}];
replacementPairs.forEach(function(pair) {
// We need the wrapper function that converts arguments to not fail. Keep adding arguments til it works.
while(1) {
try {
pair['original'].apply(object, args);
break;
} catch(e) {
args.push(args[0]);
}
}
pair.originalIndex = getValue(vTable + canaryValue*Runtime.QUANTUM_SIZE, 'void*');
});
for (var i = 0; i < size; i++) {
Runtime.removeFunction(tempFuncs[i]);
}
// Do the replacements
var replacements = {};
replacementPairs.forEach(function(pair) {
replacements[pair.originalIndex] = Runtime.addFunction(pair['replacement']);
});
// Copy and modify vtable
for (var i = 0; i < size; i++) {
var value = getValue(vTable + Runtime.QUANTUM_SIZE*i, 'void*');
if (value in replacements) value = replacements[value];
setValue(vTable2 + Runtime.QUANTUM_SIZE*i, value, 'void*');
}
return object;
}
Module['customizeVTable'] = customizeVTable;
// Converts a value into a C-style string.
function ensureString(value) {
if (typeof value == 'number') return value;
return allocate(intArrayFromString(value), 'i8', ALLOC_STACK);
}
''')
def generate_wrapping_code(classname):
return '''%(classname)s.prototype.__cache__ = {};
''' % { 'classname': classname }
# %(classname)s.prototype['fields'] = Runtime.generateStructInfo(null, '%(classname)s'); - consider adding this
def generate_class(generating_classname, classname, clazz): # TODO: deprecate generating?
print 'zz generating:', generating_classname, classname
generating_classname_head = generating_classname.split('::')[-1]
classname_head = classname.split('::')[-1]
inherited = generating_classname_head != classname_head
abstract = clazz['abstract']
if abstract:
# For abstract base classes, add a function definition on top. There is no constructor
gen_js.write('\nfunction ' + generating_classname_head + ('(){ throw "%s is abstract!" }\n' % generating_classname_head) + generate_wrapping_code(generating_classname_head))
if export:
gen_js.write('''Module['%s'] = %s;
''' % (generating_classname_head, generating_classname_head))
print 'zz methods: ', clazz['final_methods'].keys()
for method in clazz['final_methods'].itervalues():
mname = method['name']
if classname_head + '::' + mname in ignored:
print 'zz ignoring', mname
continue
params = method['parameters']
constructor = method['constructor']
destructor = method['destructor']
static = method['static']
print 'zz generating %s::%s' % (generating_classname, method['name'])
if destructor: continue
if constructor and inherited: continue
if constructor and clazz['abstract']: continue # do not generate constructors for abstract base classes
for args in params:
for i in range(len(args)):
#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()
if args[i]['name'].replace(' ', '') == '':
args[i]['name'] = 'arg' + str(i+1)
elif args[i]['name'] == '&':
args[i]['name'] = 'arg' + str(i+1)
args[i]['type'] += '&'
#print 'c1', parents.keys()
if args[i]['type'][-1] == '&':
sname = args[i]['type'][:-1]
if sname[-1] == ' ': sname = sname[:-1]
if sname in parents:
args[i]['type'] = parents[sname] + '::' + sname + '&'
elif sname.replace('const ', '') in parents:
sname = sname.replace('const ', '')
args[i]['type'] = 'const ' + parents[sname] + '::' + sname + '&'
#print 'POST arggggggg', classname, 'x', mname, 'x', args[i]['name'], 'x', args[i]['type']
ret = ((classname + ' *') if constructor else method['returns_text'])#.replace('virtual ', '')
has_return = ret.replace(' ', '') != 'void'
callprefix = 'new ' if constructor else ('self->' if not static else (classname + '::'))
'' # mname used in C
actualmname = classname if constructor else (method.get('truename') or mname)
if method.get('getter') or method.get('setter'):
actualmname = actualmname[4:]
need_self = not constructor and not static
def typedargs(args):
return ([] if not need_self else [classname + ' * self']) + map(lambda i: args[i]['type'] + ' arg' + str(i), range(len(args)))
def justargs(args):
return map(lambda i: 'arg' + str(i), range(len(args)))
def justtypes(args): # note: this ignores 'self'
return map(lambda i: args[i]['type'], range(len(args)))
def dummyargs(args):
return ([] if not need_self else ['(%s*)argv[argc]' % classname]) + map(lambda i: '(%s)argv[argc]' % args[i]['type'], range(len(args)))
fullname = ('emscripten_bind_' + generating_classname + '__' + mname).replace('::', '__')
generating_classname_suffixed = generating_classname
mname_suffixed = mname
count = funcs.setdefault(fullname, 0)
funcs[fullname] += 1
# handle overloading
dupe = False
if count > 0:
dupe = True
suffix = '_' + str(count+1)
funcs[fullname + suffix] = 0
fullname += suffix
mname_suffixed += suffix
if constructor:
generating_classname_suffixed += suffix
# C
for args in params:
i = len(args)
# 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
staticize = not constructor and ret.replace(' ', '') != 'void' and method['returns'] in classes and (not method['returns_reference'] and not method['returns_pointer'])
# Make sure to mark our bindings wrappers in a way that they will not be inlined, eliminated as unneeded, or optimized into other signatures
gen_c.write('''
%s __attribute__((used, noinline)) %s_p%d(%s) {''' % (ret if not staticize else (ret + '&'), fullname, i,
', '.join(typedargs(args)[:i + (0 if not need_self else 1)])))
if not staticize:
gen_c.write('\n')
if method.get('getter'):
gen_c.write(''' return self->%s;''' % actualmname)
elif method.get('setter'):
gen_c.write(''' self->%s = arg0;''' % actualmname)
elif method.get('destroyer'):
gen_c.write(''' delete self;''')
elif method.get('operator'):
gen_c.write(method['operator'])
else: # normal method
gen_c.write(''' %s%s%s(%s);''' % ('return ' if has_return else '',
callprefix, actualmname, ', '.join(justargs(args)[:i])))
gen_c.write('\n')
gen_c.write('}')
else:
gen_c.write('\n')
if method.get('operator'):
gen_c.write(method['operator'])
else:
gen_c.write(''' static %s ret; ret = %s%s(%s);
return ret;''' % (method['returns'],
callprefix, actualmname,
', '.join(justargs(args)[:i])))
gen_c.write('\n}')
# JS
#print 'zz types:', map(lambda arg: arg['type'], args)
has_string_convs = False
calls = ''
#print 'js loopin', params, '|', len(args)#, args
for args in params:
# We can assume that NULL is passed for null pointers, so object arguments can always
# have .ptr done on them
justargs_fixed = justargs(args)[:]
for i in range(len(args)):
arg = args[i]
clean = clean_type(arg['type'])
if clean in classes:
justargs_fixed[i] += '.ptr'
elif arg['type'].replace(' ', '').endswith('char*'):
justargs_fixed[i] = 'ensureString(' + justargs_fixed[i] + ')'
has_string_convs = True
i = len(args)
if args != params[0]:
calls += ' else '
if args != params[-1]:
calls += ' if (arg' + str(i) + ' === undefined)'
calls += '\n ' + (' ' if len(params) > 0 else '')
if constructor:
if not dupe:
calls += '''this.ptr = _%s_p%d(%s);
''' % (fullname, i, ', '.join(justargs_fixed[:i]))
else:
calls += '''this.ptr = _%s_p%d(%s);
''' % (fullname, i, ', '.join(justargs_fixed[:i]))
else:
return_value = '''_%s_p%d(%s)''' % (fullname, i, ', '.join((['this.ptr'] if need_self else []) + justargs_fixed[:i]))
print 'zz making return', classname, method['name'], method['returns'], return_value
if method['returns'] in classes:
# Generate a wrapper
calls += '''return wrapPointer(%s, Module['%s']);''' % (return_value, method['returns'].split('::')[-1])
else:
# Normal return
calls += ('return ' if ret != 'void' else '') + return_value + ';'
calls += '\n'
if has_string_convs:
calls = 'var stack = Runtime.stackSave();\ntry {\n' + calls + '} finally { Runtime.stackRestore(stack) }\n';
print 'Maekin:', classname, generating_classname, mname, mname_suffixed
if constructor:
calls += '''
%s.prototype.__cache__[this.ptr] = this;
this.__class__ = %s;''' % (mname_suffixed, mname_suffixed)
if not dupe:
js_text = '''
function %s(%s) {
%s
}
%s''' % (mname_suffixed, ', '.join(justargs(args)), calls, generate_wrapping_code(generating_classname_head))
else:
js_text = '''
function %s(%s) {
%s
}
%s.prototype = %s.prototype;
''' % (mname_suffixed, ', '.join(justargs(args)), calls, mname_suffixed, classname)
if export:
js_text += '''
Module['%s'] = %s;
''' % (mname_suffixed, mname_suffixed)
else:
js_text = '''
%s.prototype%s = function(%s) {
%s
}
''' % (generating_classname_head, ('.' + mname_suffixed) if not export else ("['" + mname_suffixed + "']"), ', '.join(justargs(args)), calls)
js_text = js_text.replace('\n\n', '\n').replace('\n\n', '\n')
gen_js.write(js_text)
# Main loop
for classname, clazz in parsed.classes.items() + parsed.structs.items():
if any([name in ignored for name in classname.split('::')]):
print 'zz ignoring', classname
continue
if clazz.get('template_typename'):
print 'zz ignoring templated base class', classname
continue
# Nothing to generate for pure virtual classes XXX actually this is not so. We do need to generate wrappers for returned objects,
# they are of a concrete class of course, but an known one, so we create a wrapper for an abstract base class.
possible_prefix = (classname.split('::')[0] + '::') if '::' in classname else ''
def check_pure_virtual(clazz, progeny):
#if not clazz.get('inherits'): return False # If no inheritance info, not a class, this is a CppHeaderParser struct
print 'Checking pure virtual for', clazz['name'], clazz['inherits']
# If we do not recognize any of the parent classes, assume this is pure virtual - ignore it
parents = [parent['class'] for parent in clazz['inherits']]
parents = [parent.split('<')[0] for parent in parents] # remove template stuff
parents = [parent if parent in classes else possible_prefix + parent for parent in parents]
if any([not parent in classes for parent in parents]):
print 'zz Warning: unknown parent class', parents, 'for', classname
return True
if any([check_pure_virtual(classes[parent], [clazz] + progeny) for parent in parents]): return True
def dirtied(mname):
#print 'zz checking dirtiness for', mname, 'in', progeny
for progen in progeny:
for method in progen['methods']:
if method['name'] == mname and not method['pure_virtual']:
#print 'zz dirty'
return True
#print 'zz not dirtied'
return False
for method in clazz['methods']:
if method['pure_virtual'] and not dirtied(method['name']):
print 'zz ignoring pure virtual class', classname, 'due to', method['name']
return True
clazz['abstract'] = check_pure_virtual(clazz, []) or clazz.get('effectively_abstract')
print 'zz', classname, 'is abstract?', clazz['abstract']
#if check_pure_virtual(clazz, []):
# continue
# Add a constructor if none exist
has_constructor = check_has_constructor(clazz)
print 'zz', classname, 'has constructor?', has_constructor
if not has_constructor:
if not clazz['abstract']:
print 'zz no constructor for', classname, 'and not abstract, so ignoring'
continue
#clazz['methods'] = [{
# 'name': classname,
# 'parameters': [],
# 'pure_virtual': False,
# 'destructor': False,
#}] + clazz['methods']
generate_class(classname, classname, clazz)
# TODO: Add a destructor
# Finish up
gen_c.write('''
}
''')
gen_c.close()
gen_js.close()