diff options
Diffstat (limited to 'tools/bindings_generator.py')
-rwxr-xr-x | tools/bindings_generator.py | 217 |
1 files changed, 186 insertions, 31 deletions
diff --git a/tools/bindings_generator.py b/tools/bindings_generator.py index 171c1d10..076c1106 100755 --- a/tools/bindings_generator.py +++ b/tools/bindings_generator.py @@ -94,6 +94,7 @@ for classname, clazz in parsed.classes.iteritems(): print 'zz see', classname classes[classname] = clazz 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] @@ -145,25 +146,97 @@ for classname, clazz in classes.iteritems(): 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, + 'num_args': set([0]), + '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, + 'num_args': set([1]), + 'parameters': [{ + 'type': type_ + ('&' if reference else ''), + 'name': 'value', + }], + }) + # 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 + for classname, clazz in parsed.classes.iteritems(): clazz['final_methods'] = {} - def explore(subclass): + 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: continue # Subclasses cannot directly use their parent's constructors if method['destructor']: continue # Nothing to do there if method['name'] not in clazz['final_methods']: - clazz['final_methods'][method['name']] = {} - for key in ['name', 'constructor', 'static', 'returns', 'returns_text', 'destructor', 'pure_virtual']: - clazz['final_methods'][method['name']][key] = method[key] - clazz['final_methods'][method['name']]['num_args'] = method['num_args'].copy() - clazz['final_methods'][method['name']]['parameters'] = method['parameters'][:] - clazz['final_methods'][method['name']]['origin'] = subclass + copied = clazz['final_methods'][method['name']] = {} + for key in ['name', 'constructor', 'static', 'returns', 'returns_text', 'returns_reference', 'returns_pointer', 'destructor', 'pure_virtual', + 'getter', 'setter']: + copied[key] = method.get(key) + copied['num_args'] = method['num_args'].copy() + copied['origin'] = subclass + copied['parameters'] = []; + # Copy the arguments, since templating may cause them to be altered + for arg in method['parameters'][:]: + copiedarg = { + 'type': arg['type'], + 'name': arg['name'], + } + copied['parameters'].append(copiedarg) + 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 arg in copied['parameters']: + arg['type'] = arg['type'].replace(template_name, template_value) + else: # Merge the new function in the best way we can. Shared arguments must match! @@ -185,10 +258,20 @@ for classname, clazz in parsed.classes.iteritems(): # Recurse if subclass.get('inherits'): for parent in subclass['inherits']: - if parent['class'] not in classes: + 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['class']]) + explore(classes[parent], template_name, template_value) explore(clazz) @@ -207,6 +290,26 @@ gen_js = open(basename + '.js', 'w') gen_c.write('extern "C" {\n') +# Use this when calling a binding function when you want to pass a null pointer. +# Having this object saves us needing to do checks for the object being null each time in the bindings code. +gen_js.write(''' +function wrapPointer(ptr) { return { ptr: ptr } }; +this['wrapPointer'] = wrapPointer; +this['NULL'] = wrapPointer(0); +''') + +def generate_wrapping_code(classname): + return '''var %(classname)s__cache__ = {}; +function %(classname)s__wrap__(ptr) { + var ret = %(classname)s__cache__[ptr]; + if (ret) return ret; + var ret = Object.create(%(classname)s.prototype); + ret.ptr = ptr; + return %(classname)s__cache__[ptr] = ret; +} +''' % { 'classname': classname } +# %(classname)s.prototype['fields'] = Runtime.generateStructInfo(null, '%(classname)s'); - consider adding this + def generate_class(generating_classname, classname, clazz): # TODO: deprecate generating? generating_classname_head = generating_classname.split('::')[-1] classname_head = classname.split('::')[-1] @@ -215,18 +318,24 @@ def generate_class(generating_classname, classname, clazz): # TODO: deprecate ge if clazz['abstract']: # For abstract base classes, add a function definition on top. There is no constructor - gen_js.write('\nfunction ' + generating_classname_head + '(){}\n') + 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('''this['%s'] = %s; +''' % (generating_classname_head, generating_classname_head)) + for method in clazz['final_methods'].itervalues(): mname = method['name'] - if classname_head + '::' + mname in ignored: continue + if classname_head + '::' + mname in ignored: + print 'zz ignoring', mname + continue args = method['parameters'] constructor = method['constructor'] destructor = method['destructor'] static = method['static'] - print "zz generating:", generating_classname, classname, mname, constructor, method['returns'], method['returns_text'] + print 'zz generating %s::%s. gets %s and returns %s' % (generating_classname, method['name'], str([arg['type'] for arg in method['parameters']]), method['returns_text']) if destructor: continue if constructor and inherited: continue @@ -262,11 +371,13 @@ def generate_class(generating_classname, classname, clazz): # TODO: deprecate ge ret = ((classname + ' *') if constructor else method['returns_text'])#.replace('virtual ', '') callprefix = 'new ' if constructor else ('self->' if not static else (classname + '::')) - actualmname = '' + actualmname = '' # mname used in C if '__operator__' in mname: continue # TODO: operators else: 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 typedargs = ([] if not need_self else [classname + ' * self']) + map(lambda arg: arg['type'] + ' ' + arg['name'], args) @@ -288,20 +399,52 @@ def generate_class(generating_classname, classname, clazz): # TODO: deprecate ge if constructor: generating_classname_suffixed += suffix - argfixes = '\n'.join(map(lambda arg: ''' %s = (%s && %s.ptr) ? %s.ptr : %s;''' % (arg['name'], arg['name'], arg['name'], arg['name'], arg['name']), args)) + # C for i in method['num_args']: - # C - + # 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']) gen_c.write(''' -%s %s_p%d(%s) { +%s %s_p%d(%s) {''' % (ret if not staticize else (ret + '&'), fullname, i, + ', '.join(typedargs[:i + (0 if not need_self else 1)]))) + if not staticize: + if method.get('getter'): + gen_c.write(''' + return self->%s; +''' % actualmname) + elif method.get('setter'): + gen_c.write(''' + self->%s = value; +''' % actualmname) + else: # normal method + gen_c.write(''' %s%s%s(%s); -} -''' % (ret, fullname, i, ', '.join(typedargs[:i + (0 if not need_self else 1)]), 'return ' if ret.replace(' ', '') != 'void' else '', callprefix, actualmname, ', '.join(justargs[:i]))) +''' % ('return ' if ret.replace(' ', '') != 'void' else '', + callprefix, actualmname, ', '.join(justargs[:i]))) + + gen_c.write('}') + else: + gen_c.write(''' + static %s ret = %s%s(%s); + return ret; +}''' % (method['returns'], + callprefix, actualmname, + ', '.join(justargs[:i]))) c_funcs.append(fullname + '_p' + str(i)) # JS + + print 'zz types:', map(lambda arg: arg['type'], args) + + # We can assume that NULL is passed for null pointers, so object arguments can always + # have .ptr done on them + justargs_fixed = justargs[:] + for i in range(len(args)): + arg = args[i] + if clean_type(arg['type']) in classes: + justargs_fixed[i] += '.ptr' + calls = '' print 'js loopin', method['num_args'], '|', len(args)#, args for i in method['num_args']: @@ -314,16 +457,16 @@ def generate_class(generating_classname, classname, clazz): # TODO: deprecate ge if constructor: if not dupe: calls += '''this.ptr = _%s_p%d(%s); -''' % (fullname, i, ', '.join(justargs[:i])) +''' % (fullname, i, ', '.join(justargs_fixed[:i])) else: calls += '''this.ptr = _%s_p%d(%s); -''' % (fullname, i, ', '.join(justargs[:i])) +''' % (fullname, i, ', '.join(justargs_fixed[:i])) else: - return_value = '''_%s_p%d(%s)''' % (fullname, i, ', '.join((['this.ptr'] if need_self else []) + justargs[:i])) + 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 += '{ var ptr = ' + return_value + '; if (!ptr) return null; var ret = Object.create(' + method['returns'] + '.prototype); ret.ptr = ptr; return ret; }' + calls += 'return %s__wrap__(%s);' % (method['returns'].split('::')[-1], return_value) else: # Normal return calls += ('return ' if ret != 'void' else '') + return_value + ';' @@ -335,17 +478,15 @@ def generate_class(generating_classname, classname, clazz): # TODO: deprecate ge js_text = ''' function %s(%s) { %s -%s } -''' % (mname_suffixed, ', '.join(justargs), argfixes, calls) +%s''' % (mname_suffixed, ', '.join(justargs), calls, generate_wrapping_code(generating_classname_head)) else: js_text = ''' function %s(%s) { %s -%s } %s.prototype = %s.prototype; -''' % (mname_suffixed, ', '.join(justargs), argfixes, calls, mname_suffixed, classname) +''' % (mname_suffixed, ', '.join(justargs), calls, mname_suffixed, classname) if export: js_text += ''' @@ -356,9 +497,8 @@ this['%s'] = %s; js_text = ''' %s.prototype%s = function(%s) { %s -%s } -''' % (generating_classname_head, ('.' + mname_suffixed) if not export else ("['" + mname_suffixed + "']"), ', '.join(justargs), argfixes, calls) +''' % (generating_classname_head, ('.' + mname_suffixed) if not export else ("['" + mname_suffixed + "']"), ', '.join(justargs), calls) js_text = js_text.replace('\n\n', '\n').replace('\n\n', '\n') gen_js.write(js_text) @@ -366,16 +506,30 @@ this['%s'] = %s; # Main loop for classname, clazz in classes.iteritems(): - if any([name in ignored for name in classname.split('::')]): continue + 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 - if any([((not parent['class'] in classes) or check_pure_virtual(classes[parent['class']], [clazz] + progeny)) for parent in clazz['inherits']]): return True + 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 @@ -393,6 +547,7 @@ for classname, clazz in classes.iteritems(): return True clazz['abstract'] = check_pure_virtual(clazz, []) + print 'zz', classname, 'is abstract?', clazz['abstract'] #if check_pure_virtual(clazz, []): # continue |