| #!/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() |
| |