Revision 84f01eb74ec336f97645ddeb2315a08b737cfd6b authored by Dan Gohman on 04 March 2014, 08:09:11 UTC, committed by Dan Gohman on 04 March 2014, 08:09:11 UTC
1 parent a7f7be8
Raw File
bindings_generator.py
#!/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()

back to top