~ubuntu-branches/ubuntu/vivid/emscripten/vivid

« back to all changes in this revision

Viewing changes to tools/bindings_generator.py

  • Committer: Package Import Robot
  • Author(s): Sylvestre Ledru
  • Date: 2013-05-02 13:11:51 UTC
  • Revision ID: package-import@ubuntu.com-20130502131151-q8dvteqr1ef2x7xz
Tags: upstream-1.4.1~20130504~adb56cb
ImportĀ upstreamĀ versionĀ 1.4.1~20130504~adb56cb

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python2
 
2
 
 
3
'''
 
4
Use CppHeaderParser to parse some C++ headers, and generate binding code for them.
 
5
 
 
6
Usage:
 
7
        bindings_generator.py BASENAME HEADER1 HEADER2 ... [-- JSON]
 
8
 
 
9
  BASENAME is the name used for output files (with added suffixes).
 
10
  HEADER1 etc. are the C++ headers to parse
 
11
 
 
12
We generate the following:
 
13
 
 
14
  * BASENAME.c: C bindings file, with generated C wrapper functions. You will
 
15
                need to build this with your project, and make sure it compiles
 
16
                properly by adding the proper #includes etc. You can also just
 
17
                #include this file itself in one of your existing project files.
 
18
 
 
19
  * BASENAME.js: JavaScript bindings file, with generated JavaScript wrapper
 
20
                 objects. This is a high-level wrapping, using native JS classes.
 
21
 
 
22
  * JSON: An optional JSON object with various optional options:
 
23
 
 
24
            ignored: A list of classes and class::methods not to generate code for.
 
25
                     Comma separated.
 
26
            
 
27
            type_processor: Text that is eval()d into a lambda that is run on
 
28
                            all arguments. For example, you can use this to
 
29
                            change all arguments of type float& to float by
 
30
                            "type_processor": "lambda t: t if t != 'float&' else 'float'"
 
31
 
 
32
            export: If false, will not export all bindings in the .js file. The
 
33
                    default is to export all bindings, which allows
 
34
                    you to run something like closure compiler advanced opts on
 
35
                    the library+bindings, and the bindings will remain accessible.
 
36
 
 
37
          For example, JSON can be { "ignored": "class1,class2::func" }.
 
38
 
 
39
The C bindings file is basically a tiny C wrapper around the C++ code.
 
40
It's only purpose is to make it easy to access the C++ code in the JS
 
41
bindings, and to prevent DFE from removing the code we care about. The
 
42
JS bindings do more serious work, creating class structures in JS and
 
43
linking them to the C bindings.
 
44
 
 
45
Notes:
 
46
 * ammo.js is currently the biggest consumer of this code. For some
 
47
    more docs you can see that project's README,
 
48
 
 
49
      https://github.com/kripken/ammo.js
 
50
 
 
51
    Another project using these bindings is box2d.js,
 
52
 
 
53
      https://github.com/kripken/box2d.js
 
54
 
 
55
 * Functions implemented inline in classes may not be actually
 
56
    compiled into bitcode, unless they are actually used. That may
 
57
    confuse the bindings generator.
 
58
 
 
59
 * C strings (char *) passed to functions are treated in a special way.
 
60
    If you pass in a pointer, it is assumed to be a pointer and left as
 
61
    is. Otherwise it must be a JS string, and we convert it to a C
 
62
    string on the *stack*. The C string will live for the current
 
63
    function call. If you need it for longer, you need to create a copy
 
64
    in your C++ code.
 
65
 
 
66
 * You should compile with -s EXPORT_BINDINGS=1 in order for the
 
67
   autogenerated bindings functions to be exported. This is necessary
 
68
   when compiling in asm.js mode.
 
69
'''
 
70
 
 
71
import os, sys, glob, re
 
72
 
 
73
__rootpath__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
74
def path_from_root(*pathelems):
 
75
  return os.path.join(__rootpath__, *pathelems)
 
76
sys.path += [path_from_root('')]
 
77
from tools.shared import *
 
78
 
 
79
# Find ply and CppHeaderParser
 
80
sys.path = [path_from_root('third_party', 'ply'), path_from_root('third_party', 'CppHeaderParser')] + sys.path
 
81
import CppHeaderParser
 
82
 
 
83
#print glob.glob(path_from_root('tests', 'bullet', 'src', 'BulletCollision', 'CollisionDispatch', '*.h'))
 
84
 
 
85
basename = sys.argv[1]
 
86
 
 
87
ignored = []
 
88
type_processor = lambda t: t
 
89
export = 1
 
90
 
 
91
if '--' in sys.argv:
 
92
  index = sys.argv.index('--')
 
93
  json = eval(sys.argv[index+1])
 
94
  sys.argv = sys.argv[:index]
 
95
 
 
96
  if json.get('ignored'):
 
97
    ignored = json['ignored'].split(',')
 
98
  if json.get('type_processor'):
 
99
    type_processor = eval(json['type_processor'])
 
100
  if json.get('export'):
 
101
    export = json['export']
 
102
 
 
103
  print 'zz ignoring', ignored
 
104
 
 
105
# First pass - read everything
 
106
 
 
107
classes = {}
 
108
parents = {}
 
109
 
 
110
text = ''
 
111
for header in sys.argv[2:]:
 
112
  text += '//// ' + header + '\n'
 
113
  text += open(header, 'r').read()
 
114
all_h_name = basename + '.all.h'
 
115
all_h = open(all_h_name, 'w')
 
116
all_h.write(text)
 
117
all_h.close()
 
118
 
 
119
parsed = CppHeaderParser.CppHeader(all_h_name)
 
120
print 'zz dir: ', parsed.__dict__.keys()
 
121
for classname, clazz in parsed.classes.items() + parsed.structs.items():
 
122
  print 'zz see', classname, clazz, type(clazz)
 
123
  classes[classname] = clazz
 
124
  if type(clazz['methods']) == dict:
 
125
    clazz['saved_methods'] = clazz['methods']
 
126
    clazz['methods'] = clazz['methods']['public'] # CppHeaderParser doesn't have 'public' etc. in structs. so equalize to that
 
127
 
 
128
  if '::' in classname:
 
129
    assert classname.count('::') == 1
 
130
    parents[classname.split('::')[1]] = classname.split('::')[0]
 
131
 
 
132
  if hasattr(clazz, '_public_structs'): # This is a class
 
133
    for sname, struct in clazz._public_structs.iteritems():
 
134
      parents[sname] = classname
 
135
      classes[classname + '::' + sname] = struct
 
136
      struct['name'] = sname # Missing in CppHeaderParser
 
137
      print 'zz seen struct %s in %s' % (sname, classname)
 
138
 
 
139
  if 'fields' in clazz: # This is a struct
 
140
    print 'zz add properties!'
 
141
    clazz['properties'] = { 'public': clazz['fields'] }
 
142
    clazz['name'] = classname
 
143
    clazz['inherits'] = []
 
144
print 'zz parents: ', parents
 
145
 
 
146
def check_has_constructor(clazz):
 
147
  for method in clazz['methods']:
 
148
    if method['constructor'] and not method['destructor']: return True
 
149
  return False
 
150
 
 
151
for classname, clazz in parsed.classes.items() + parsed.structs.items():
 
152
  # Various precalculations
 
153
  print 'zz precalc', classname
 
154
  for method in clazz['methods'][:]:
 
155
    method['constructor'] = method['constructor'] or (method['name'] == classname) # work around cppheaderparser issue
 
156
    print 'z constructorhmm?', method['name'], method['constructor']#, constructor, method['name'], classname
 
157
    args = method['parameters']
 
158
 
 
159
    #if method['name'] == 'addWheel': print 'qqqq', classname, method
 
160
 
 
161
    # Fill in some missing stuff
 
162
    for i in range(len(args)):
 
163
      if args[i]['pointer'] and '*' not in args[i]['type']:
 
164
        args[i]['type'] += '*'
 
165
      if args[i]['reference'] and '&' not in args[i]['type']:
 
166
        args[i]['type'] += '&'
 
167
      args[i]['type'] = type_processor(args[i]['type'])
 
168
      #raw = args[i]['type'].replace('&', '').replace('*', '')
 
169
      #if raw in classes:
 
170
 
 
171
    default_param = len(args)+1
 
172
    for i in range(len(args)):
 
173
      if args[i].get('default'):
 
174
        default_param = i+1
 
175
        break
 
176
 
 
177
    method['parameters'] = [args[:i] for i in range(default_param-1, len(args)+1)]
 
178
    print 'zz ', classname, 'has parameters in range', range(default_param-1, len(args)+1)
 
179
 
 
180
    method['returns_text'] = method['returns']
 
181
    if method['static']:
 
182
      method['returns_text'] = method['returns_text'].replace('static', '')
 
183
 
 
184
    # Implement operators
 
185
    if '__operator__' in method['name']:
 
186
      if 'assignment' in method['name']:
 
187
        method['name'] = 'op_set'
 
188
        method['operator'] = '  return *self = arg0;'
 
189
      elif 'add' in method['name']:
 
190
        method['name'] = 'op_add'
 
191
        method['operator'] = '  return *self += arg0;'
 
192
      elif 'sub' in method['name']:
 
193
        print 'zz subsubsub ', classname, method['name'], method['parameters'][0]
 
194
        method['name'] = 'op_sub'
 
195
        if len(method['parameters'][0]) == 0:
 
196
          method['operator'] = '  static %s ret; ret = -*self; return ret;' % method['returns']
 
197
        else:
 
198
          method['operator'] = '  return *self -= arg0; // %d : %s' % (len(method['parameters'][0]), method['parameters'][0][0]['name'])
 
199
      elif 'imul' in method['name']:
 
200
        method['name'] = 'op_mul'
 
201
        method['operator'] = '  return *self *= arg0;'
 
202
      elif 'mul' in method['name']:
 
203
        method['name'] = 'op_mul'
 
204
        method['operator'] = '  static %s ret; ret = *self * arg0; return ret;' % method['returns']
 
205
      elif 'div' in method['name']:
 
206
        method['name'] = 'op_div'
 
207
        method['operator'] = '  return *self /= arg0;'
 
208
      elif 'getitem' in method['name']:
 
209
        method['name'] = 'op_get'
 
210
        method['operator'] = '  return (*self)[arg0];'
 
211
      elif 'delete' in method['name']:
 
212
        method['ignore'] = True
 
213
      elif 'new' in method['name']:
 
214
        method['ignore'] = True
 
215
      elif 'eq' in method['name']:
 
216
        method['name'] = 'op_comp'
 
217
        method['operator'] = '  return arg0 == arg1;' if len(method['parameters'][0]) == 2 else '  return *self == arg0;'
 
218
      else:
 
219
        print 'zz unknown operator:', method['name'], ', ignoring'
 
220
        method['ignore'] = True
 
221
 
 
222
    # Fill in some missing stuff
 
223
    method['returns_text'] = method['returns_text'].replace('&', '').replace('*', '')
 
224
    if method['returns_text'] in parents:
 
225
      method['returns_text'] = parents[method['returns_text']] + '::' + method['returns_text']
 
226
    if method.get('returns_const'): method['returns_text'] = 'const ' + method['returns_text']
 
227
    if method.get('returns_pointer'):
 
228
      while method['returns_text'].count('*') < method['returns_pointer']:
 
229
        method['returns_text'] += '*'
 
230
    if method.get('returns_reference'): method['returns_text'] += '&'
 
231
    method['returns_text'] = type_processor(method['returns_text'])
 
232
 
 
233
    #print 'zz %s::%s gets %s and returns %s' % (classname, method['name'], str([arg['type'] for arg in method['parameters']]), method['returns_text'])
 
234
 
 
235
    # Add getters/setters for public members
 
236
    for prop in clazz['properties']['public']:
 
237
      if classname + '::' + prop['name'] in ignored: continue
 
238
      if prop.get('array_dimensions'):
 
239
        print 'zz warning: ignoring getter/setter for array', classname + '::' + prop['name']
 
240
        continue
 
241
      type_ = prop['type'].replace('mutable ', '')#.replace(' ', '')
 
242
      if '<' in prop['name'] or '<' in type_:
 
243
        print 'zz warning: ignoring getter/setter for templated class', classname + '::' + prop['name']
 
244
        continue
 
245
      reference = type_ in classes # a raw struct or class as a prop means we need to work with a ref
 
246
      clazz['methods'].append({
 
247
        'getter': True,
 
248
        'name': 'get_' + prop['name'],
 
249
        'constructor': False,
 
250
        'destructor': False,
 
251
        'static': False,
 
252
        'returns': type_.replace(' *', '').replace('*', ''),
 
253
        'returns_text': type_ + ('&' if reference else ''),
 
254
        'returns_reference': reference,
 
255
        'returns_pointer': '*' in type_,
 
256
        'pure_virtual': False,
 
257
        'parameters': [[]],
 
258
      })
 
259
      clazz['methods'].append({
 
260
        'setter': True,
 
261
        'name': 'set_' + prop['name'],
 
262
        'constructor': False,
 
263
        'destructor': False,
 
264
        'static': False,
 
265
        'returns': 'void',
 
266
        'returns_text': 'void',
 
267
        'returns_reference': False,
 
268
        'returns_pointer': False,
 
269
        'pure_virtual': False,
 
270
        'parameters': [[{
 
271
          'type': type_ + ('&' if reference else ''),
 
272
          'name': 'value',
 
273
        }]],
 
274
      })
 
275
 
 
276
  print 'zz is effectively abstract?', clazz['name'], classname, '0'
 
277
  if 'saved_methods' in clazz and not check_has_constructor(clazz):
 
278
    print 'zz is effectively abstract?', clazz['name'], '1'
 
279
    # Having a private constructor and no public constructor means you are, in effect, abstract
 
280
    for private_method in clazz['saved_methods']['private']:
 
281
      print 'zz is effectively abstract?', clazz['name'], '2'
 
282
      if private_method['constructor']:
 
283
        print 'zz is effectively abstract?', clazz['name'], '3'
 
284
        clazz['effectively_abstract'] = True
 
285
 
 
286
  # Add destroyer
 
287
  if not clazz.get('abstract') and not clazz.get('effectively_abstract'):
 
288
    clazz['methods'].append({
 
289
      'destroyer': True,
 
290
      'name': '__destroy__',
 
291
      'constructor': False,
 
292
      'destructor': False,
 
293
      'static': False,
 
294
      'returns': 'void',
 
295
      'returns_text': 'void',
 
296
      'returns_reference': False,
 
297
      'returns_pointer': False,
 
298
      'pure_virtual': False,
 
299
      'parameters': [[]],
 
300
    })
 
301
 
 
302
  clazz['methods'] = filter(lambda method: not method.get('ignore'), clazz['methods'])
 
303
 
 
304
# Explore all functions we need to generate, including parent classes, handling of overloading, etc.
 
305
 
 
306
def clean_type(t):
 
307
  return t.replace('const ', '').replace('struct ', '').replace('&', '').replace('*', '').replace(' ', '')
 
308
 
 
309
def fix_template_value(t): # Not sure why this is needed, might be a bug in CppHeaderParser
 
310
  if t == 'unsignedshortint':
 
311
    return 'unsigned short int'
 
312
  elif t == 'unsignedint':
 
313
    return 'unsigned int'
 
314
  return t
 
315
 
 
316
def copy_args(args):
 
317
  ret = []
 
318
  for arg in args:
 
319
    copiedarg = {
 
320
      'type': arg['type'],
 
321
      'name': arg['name'],
 
322
    }
 
323
    ret.append(copiedarg)
 
324
  return ret
 
325
 
 
326
for classname, clazz in parsed.classes.items() + parsed.structs.items():
 
327
  clazz['final_methods'] = {}
 
328
 
 
329
  def explore(subclass, template_name=None, template_value=None):
 
330
    # Do our functions first, and do not let later classes override
 
331
    for method in subclass['methods']:
 
332
      print classname, 'exploring', subclass['name'], '::', method['name']
 
333
 
 
334
      if method['constructor']:
 
335
        if clazz != subclass:
 
336
          print "zz Subclasses cannot directly use their parent's constructors"
 
337
          continue
 
338
      if method['destructor']:
 
339
        print 'zz Nothing to do there'
 
340
        continue
 
341
 
 
342
      if method.get('operator') and subclass is not clazz:
 
343
        print 'zz Do not use parent class operators. Cast to that class if you need those operators (castObject)'
 
344
        continue
 
345
 
 
346
      if method['name'] not in clazz['final_methods']:
 
347
        copied = clazz['final_methods'][method['name']] = {}
 
348
        for key in ['name', 'constructor', 'static', 'returns', 'returns_text', 'returns_reference', 'returns_pointer', 'destructor', 'pure_virtual',
 
349
                    'getter', 'setter', 'destroyer', 'operator']:
 
350
          copied[key] = method.get(key)
 
351
        copied['origin'] = subclass
 
352
        copied['parameters'] = [];
 
353
        for args in method['parameters']:
 
354
          # Copy the arguments, since templating may cause them to be altered
 
355
          copied['parameters'].append(copy_args(args))
 
356
        if template_name:
 
357
          # Set template values
 
358
          copied['returns'] = copied['returns'].replace(template_name, template_value)
 
359
          copied['returns_text'] = copied['returns_text'].replace(template_name, template_value)
 
360
          for args in copied['parameters']:
 
361
            for arg in args:
 
362
              arg['type'] = arg['type'].replace(template_name, template_value)
 
363
      else:
 
364
        # Merge the new function in the best way we can. Two signatures (args) must differ in their number
 
365
 
 
366
        if method.get('operator'): continue # do not merge operators
 
367
 
 
368
        curr = clazz['final_methods'][method['name']]
 
369
 
 
370
        if curr['origin'] is not subclass: continue # child class functions mask/hide parent functions of the same name in C++
 
371
 
 
372
        problem = False
 
373
        for curr_args in curr['parameters']:
 
374
          for method_args in method['parameters']:
 
375
            if len(curr_args) == len(method_args):
 
376
              problem = True
 
377
        if problem:
 
378
          print 'Warning: Cannot mix in overloaded functions', method['name'], 'in class', classname, ', skipping'
 
379
          continue
 
380
        # TODO: Other compatibility checks, if any?
 
381
 
 
382
        curr['parameters'] += map(copy_args, method['parameters'])
 
383
 
 
384
        print 'zz ', classname, 'has updated parameters of ', curr['parameters']
 
385
 
 
386
    # Recurse
 
387
    if subclass.get('inherits'):
 
388
      for parent in subclass['inherits']:
 
389
        parent = parent['class']
 
390
        template_name = None
 
391
        template_value = None
 
392
        if '<' in parent:
 
393
          parent, template = parent.split('<')
 
394
          template_name = classes[parent]['template_typename']
 
395
          template_value = fix_template_value(template.replace('>', ''))
 
396
          print 'template', template_value, 'for', classname, '::', parent, ' | ', template_name
 
397
        if parent not in classes and '::' in classname: # They might both be subclasses in the same parent
 
398
          parent = classname.split('::')[0] + '::' + parent
 
399
        if parent not in classes:
 
400
          print 'Warning: parent class', parent, 'not a known class. Ignoring.'
 
401
          return
 
402
        explore(classes[parent], template_name, template_value)
 
403
 
 
404
  explore(clazz)
 
405
 
 
406
  for method in clazz['final_methods'].itervalues():
 
407
    method['parameters'].sort(key=len)
 
408
 
 
409
# Second pass - generate bindings
 
410
# TODO: Bind virtual functions using dynamic binding in the C binding code
 
411
 
 
412
funcs = {} # name -> # of copies in the original, and originalname in a copy
 
413
 
 
414
gen_c = open(basename + '.cpp', 'w')
 
415
gen_js = open(basename + '.js', 'w')
 
416
 
 
417
gen_c.write('extern "C" {\n')
 
418
 
 
419
gen_js.write('''
 
420
// Bindings utilities
 
421
 
 
422
var Object__cache = {}; // we do it this way so we do not modify |Object|
 
423
function wrapPointer(ptr, __class__) {
 
424
  var cache = __class__ ? __class__.prototype.__cache__ : Object__cache;
 
425
  var ret = cache[ptr];
 
426
  if (ret) return ret;
 
427
  __class__ = __class__ || Object;
 
428
  ret = Object.create(__class__.prototype);
 
429
  ret.ptr = ptr;
 
430
  ret.__class__ = __class__;
 
431
  return cache[ptr] = ret;
 
432
}
 
433
Module['wrapPointer'] = wrapPointer;
 
434
 
 
435
function castObject(obj, __class__) {
 
436
  return wrapPointer(obj.ptr, __class__);
 
437
}
 
438
Module['castObject'] = castObject;
 
439
 
 
440
Module['NULL'] = wrapPointer(0);
 
441
 
 
442
function destroy(obj) {
 
443
  if (!obj['__destroy__']) throw 'Error: Cannot destroy object. (Did you create it yourself?)';
 
444
  obj['__destroy__']();
 
445
  // Remove from cache, so the object can be GC'd and refs added onto it released
 
446
  if (obj.__class__ !== Object) {
 
447
    delete obj.__class__.prototype.__cache__[obj.ptr];
 
448
  } else {
 
449
    delete Object__cache[obj.ptr];
 
450
  }
 
451
}
 
452
Module['destroy'] = destroy;
 
453
 
 
454
function compare(obj1, obj2) {
 
455
  return obj1.ptr === obj2.ptr;
 
456
}
 
457
Module['compare'] = compare;
 
458
 
 
459
function getPointer(obj) {
 
460
  return obj.ptr;
 
461
}
 
462
Module['getPointer'] = getPointer;
 
463
 
 
464
function getClass(obj) {
 
465
  return obj.__class__;
 
466
}
 
467
Module['getClass'] = getClass;
 
468
 
 
469
function customizeVTable(object, replacementPairs) {
 
470
  // Does not handle multiple inheritance
 
471
  // Does not work with asm.js
 
472
 
 
473
  // Find out vtable size
 
474
  var vTable = getValue(object.ptr, 'void*');
 
475
  // This assumes our modification where we null-terminate vtables
 
476
  var size = 0;
 
477
  while (getValue(vTable + Runtime.QUANTUM_SIZE*size, 'void*')) {
 
478
    size++;
 
479
  }
 
480
 
 
481
  // Prepare replacement lookup table and add replacements to FUNCTION_TABLE
 
482
  // There is actually no good way to do this! So we do the following hack:
 
483
  // We create a fake vtable with canary functions, to detect which actual
 
484
  // function is being called
 
485
  var vTable2 = _malloc(size*Runtime.QUANTUM_SIZE);
 
486
  setValue(object.ptr, vTable2, 'void*');
 
487
  var canaryValue;
 
488
  var functions = FUNCTION_TABLE.length;
 
489
  for (var i = 0; i < size; i++) {
 
490
    var index = FUNCTION_TABLE.length;
 
491
    (function(j) {
 
492
      FUNCTION_TABLE.push(function() {
 
493
        canaryValue = j;
 
494
      });
 
495
    })(i);
 
496
    FUNCTION_TABLE.push(0);
 
497
    setValue(vTable2 + Runtime.QUANTUM_SIZE*i, index, 'void*');
 
498
  }
 
499
  var args = [{ptr: 0}];
 
500
  replacementPairs.forEach(function(pair) {
 
501
    // We need the wrapper function that converts arguments to not fail. Keep adding arguments til it works.
 
502
    while(1) {
 
503
      try {
 
504
        pair['original'].apply(object, args);
 
505
        break;
 
506
      } catch(e) {
 
507
        args.push(args[0]);
 
508
      }
 
509
    }
 
510
    pair.originalIndex = getValue(vTable + canaryValue*Runtime.QUANTUM_SIZE, 'void*');
 
511
  });
 
512
  FUNCTION_TABLE = FUNCTION_TABLE.slice(0, functions);
 
513
 
 
514
  // Do the replacements
 
515
 
 
516
  var replacements = {};
 
517
  replacementPairs.forEach(function(pair) {
 
518
    var replacementIndex = FUNCTION_TABLE.length;
 
519
    FUNCTION_TABLE.push(pair['replacement']);
 
520
    FUNCTION_TABLE.push(0);
 
521
    replacements[pair.originalIndex] = replacementIndex;
 
522
  });
 
523
 
 
524
  // Copy and modify vtable
 
525
  for (var i = 0; i < size; i++) {
 
526
    var value = getValue(vTable + Runtime.QUANTUM_SIZE*i, 'void*');
 
527
    if (value in replacements) value = replacements[value];
 
528
    setValue(vTable2 + Runtime.QUANTUM_SIZE*i, value, 'void*');
 
529
  }
 
530
  return object;
 
531
}
 
532
Module['customizeVTable'] = customizeVTable;
 
533
 
 
534
// Converts a value into a C-style string.
 
535
function ensureString(value) {
 
536
  if (typeof value == 'number') return value;
 
537
  return allocate(intArrayFromString(value), 'i8', ALLOC_STACK);
 
538
}
 
539
''')
 
540
 
 
541
def generate_wrapping_code(classname):
 
542
  return '''%(classname)s.prototype.__cache__ = {};
 
543
''' % { 'classname': classname }
 
544
# %(classname)s.prototype['fields'] = Runtime.generateStructInfo(null, '%(classname)s'); - consider adding this
 
545
 
 
546
def generate_class(generating_classname, classname, clazz): # TODO: deprecate generating?
 
547
  print 'zz generating:', generating_classname, classname
 
548
  generating_classname_head = generating_classname.split('::')[-1]
 
549
  classname_head = classname.split('::')[-1]
 
550
 
 
551
  inherited = generating_classname_head != classname_head
 
552
 
 
553
  abstract = clazz['abstract']
 
554
  if abstract:
 
555
    # For abstract base classes, add a function definition on top. There is no constructor
 
556
    gen_js.write('\nfunction ' + generating_classname_head + ('(){ throw "%s is abstract!" }\n' % generating_classname_head) + generate_wrapping_code(generating_classname_head))
 
557
    if export:
 
558
      gen_js.write('''Module['%s'] = %s;
 
559
''' % (generating_classname_head, generating_classname_head))
 
560
 
 
561
  print 'zz methods: ', clazz['final_methods'].keys()
 
562
  for method in clazz['final_methods'].itervalues():
 
563
    mname = method['name']
 
564
    if classname_head + '::' + mname in ignored:
 
565
      print 'zz ignoring', mname
 
566
      continue
 
567
 
 
568
    params = method['parameters']
 
569
    constructor = method['constructor']
 
570
    destructor = method['destructor']
 
571
    static = method['static']
 
572
 
 
573
    print 'zz generating %s::%s' % (generating_classname, method['name'])
 
574
 
 
575
    if destructor: continue
 
576
    if constructor and inherited: continue
 
577
    if constructor and clazz['abstract']: continue # do not generate constructors for abstract base classes
 
578
 
 
579
    for args in params:
 
580
      for i in range(len(args)):
 
581
        #print 'zz   arggggggg', classname, 'x', mname, 'x', args[i]['name'], 'x', args[i]['type'], 'x', dir(args[i]), 'y', args[i].get('default'), 'z', args[i].get('defaltValue'), args[i].keys()
 
582
 
 
583
        if args[i]['name'].replace(' ', '') == '':
 
584
          args[i]['name'] = 'arg' + str(i+1)
 
585
        elif args[i]['name'] == '&':
 
586
          args[i]['name'] = 'arg' + str(i+1)
 
587
          args[i]['type'] += '&'
 
588
 
 
589
        #print 'c1', parents.keys()
 
590
        if args[i]['type'][-1] == '&':
 
591
          sname = args[i]['type'][:-1]
 
592
          if sname[-1] == ' ': sname = sname[:-1]
 
593
          if sname in parents:
 
594
            args[i]['type'] = parents[sname] + '::' + sname + '&'
 
595
          elif sname.replace('const ', '') in parents:
 
596
            sname = sname.replace('const ', '')
 
597
            args[i]['type'] = 'const ' + parents[sname] + '::' + sname + '&'
 
598
        #print 'POST arggggggg', classname, 'x', mname, 'x', args[i]['name'], 'x', args[i]['type']
 
599
 
 
600
    ret = ((classname + ' *') if constructor else method['returns_text'])#.replace('virtual ', '')
 
601
    has_return = ret.replace(' ', '') != 'void'
 
602
    callprefix = 'new ' if constructor else ('self->' if not static else (classname + '::'))
 
603
 
 
604
    '' # mname used in C
 
605
    actualmname = classname if constructor else (method.get('truename') or mname)
 
606
    if method.get('getter') or method.get('setter'):
 
607
      actualmname = actualmname[4:]
 
608
 
 
609
    need_self = not constructor and not static
 
610
 
 
611
    def typedargs(args):
 
612
      return ([] if not need_self else [classname + ' * self']) + map(lambda i: args[i]['type'] + ' arg' + str(i), range(len(args)))
 
613
    def justargs(args):
 
614
      return map(lambda i: 'arg' + str(i), range(len(args)))
 
615
    def justtypes(args): # note: this ignores 'self'
 
616
      return map(lambda i: args[i]['type'], range(len(args)))
 
617
    def dummyargs(args):
 
618
      return ([] if not need_self else ['(%s*)argv[argc]' % classname]) + map(lambda i: '(%s)argv[argc]' % args[i]['type'], range(len(args)))
 
619
 
 
620
    fullname = ('emscripten_bind_' + generating_classname + '__' + mname).replace('::', '__')
 
621
    generating_classname_suffixed = generating_classname
 
622
    mname_suffixed = mname
 
623
    count = funcs.setdefault(fullname, 0)
 
624
    funcs[fullname] += 1
 
625
 
 
626
    # handle overloading
 
627
    dupe = False
 
628
    if count > 0:
 
629
      dupe = True
 
630
      suffix = '_' + str(count+1)
 
631
      funcs[fullname + suffix] = 0
 
632
      fullname += suffix
 
633
      mname_suffixed += suffix
 
634
      if constructor:
 
635
        generating_classname_suffixed += suffix
 
636
 
 
637
    # C
 
638
 
 
639
    for args in params:
 
640
      i = len(args)
 
641
      # If we are returning a *copy* of an object, we return instead to a ref of a static held here. This seems the best compromise
 
642
      staticize = not constructor and ret.replace(' ', '') != 'void' and method['returns'] in classes and (not method['returns_reference'] and not method['returns_pointer'])
 
643
      # Make sure to mark our bindings wrappers in a way that they will not be inlined, eliminated as unneeded, or optimized into other signatures
 
644
      gen_c.write('''
 
645
%s __attribute__((used, noinline)) %s_p%d(%s) {''' % (ret if not staticize else (ret + '&'), fullname, i,
 
646
                    ', '.join(typedargs(args)[:i + (0 if not need_self else 1)])))
 
647
      if not staticize:
 
648
        gen_c.write('\n')
 
649
        if method.get('getter'):
 
650
          gen_c.write('''  return self->%s;''' % actualmname)
 
651
        elif method.get('setter'):
 
652
          gen_c.write('''  self->%s = arg0;''' % actualmname)
 
653
        elif method.get('destroyer'):
 
654
          gen_c.write('''  delete self;''')
 
655
        elif method.get('operator'):
 
656
          gen_c.write(method['operator'])
 
657
        else: # normal method
 
658
          gen_c.write('''  %s%s%s(%s);''' % ('return ' if has_return else '',
 
659
                                             callprefix, actualmname, ', '.join(justargs(args)[:i])))
 
660
        gen_c.write('\n')
 
661
        gen_c.write('}')
 
662
      else:
 
663
        gen_c.write('\n')
 
664
        if method.get('operator'):
 
665
          gen_c.write(method['operator'])
 
666
        else:
 
667
          gen_c.write('''  static %s ret; ret = %s%s(%s);
 
668
  return ret;''' % (method['returns'],
 
669
        callprefix, actualmname,
 
670
        ', '.join(justargs(args)[:i])))
 
671
        gen_c.write('\n}')
 
672
 
 
673
    # JS
 
674
 
 
675
    #print 'zz types:', map(lambda arg: arg['type'], args)
 
676
 
 
677
    has_string_convs = False
 
678
 
 
679
    calls = ''
 
680
 
 
681
    #print 'js loopin', params, '|', len(args)#, args
 
682
    for args in params:
 
683
      # We can assume that NULL is passed for null pointers, so object arguments can always
 
684
      # have .ptr done on them
 
685
      justargs_fixed = justargs(args)[:]
 
686
      for i in range(len(args)):
 
687
        arg = args[i]
 
688
        clean = clean_type(arg['type'])
 
689
        if clean in classes:
 
690
          justargs_fixed[i] += '.ptr'
 
691
        elif arg['type'].replace(' ', '').endswith('char*'):
 
692
          justargs_fixed[i] = 'ensureString(' + justargs_fixed[i] + ')'
 
693
          has_string_convs = True
 
694
 
 
695
      i = len(args)
 
696
      if args != params[0]:
 
697
        calls += '  else '
 
698
      if args != params[-1]:
 
699
        calls += '  if (arg' + str(i) + ' === undefined)'
 
700
      calls += '\n  ' + ('  ' if len(params) > 0 else '')
 
701
      if constructor:
 
702
        if not dupe:
 
703
          calls += '''this.ptr = _%s_p%d(%s);
 
704
''' % (fullname, i, ', '.join(justargs_fixed[:i]))
 
705
        else:
 
706
          calls += '''this.ptr = _%s_p%d(%s);
 
707
''' % (fullname, i, ', '.join(justargs_fixed[:i]))
 
708
      else:
 
709
        return_value = '''_%s_p%d(%s)''' % (fullname, i, ', '.join((['this.ptr'] if need_self else []) + justargs_fixed[:i]))
 
710
        print 'zz making return', classname, method['name'], method['returns'], return_value
 
711
        if method['returns'] in classes:
 
712
          # Generate a wrapper
 
713
          calls += '''return wrapPointer(%s, Module['%s']);''' % (return_value, method['returns'].split('::')[-1])
 
714
        else:
 
715
          # Normal return
 
716
          calls += ('return ' if ret != 'void' else '') + return_value + ';'
 
717
        calls += '\n'
 
718
 
 
719
    if has_string_convs:
 
720
      calls = 'var stack = Runtime.stackSave();\ntry {\n' + calls + '} finally { Runtime.stackRestore(stack) }\n';
 
721
 
 
722
    print 'Maekin:', classname, generating_classname, mname, mname_suffixed
 
723
    if constructor:
 
724
      calls += '''
 
725
  %s.prototype.__cache__[this.ptr] = this;
 
726
  this.__class__ = %s;''' % (mname_suffixed, mname_suffixed)
 
727
      if not dupe:
 
728
        js_text = '''
 
729
function %s(%s) {
 
730
%s
 
731
}
 
732
%s''' % (mname_suffixed, ', '.join(justargs(args)), calls, generate_wrapping_code(generating_classname_head))
 
733
      else:
 
734
        js_text = '''
 
735
function %s(%s) {
 
736
%s
 
737
}
 
738
%s.prototype = %s.prototype;
 
739
''' % (mname_suffixed, ', '.join(justargs(args)), calls, mname_suffixed, classname)
 
740
 
 
741
      if export:
 
742
        js_text += '''
 
743
Module['%s'] = %s;
 
744
''' % (mname_suffixed, mname_suffixed)
 
745
 
 
746
    else:
 
747
      js_text = '''
 
748
%s.prototype%s = function(%s) {
 
749
%s
 
750
}
 
751
''' % (generating_classname_head, ('.' + mname_suffixed) if not export else ("['" + mname_suffixed + "']"), ', '.join(justargs(args)), calls)
 
752
 
 
753
    js_text = js_text.replace('\n\n', '\n').replace('\n\n', '\n')
 
754
    gen_js.write(js_text)
 
755
 
 
756
# Main loop
 
757
 
 
758
for classname, clazz in parsed.classes.items() + parsed.structs.items():
 
759
  if any([name in ignored for name in classname.split('::')]):
 
760
    print 'zz ignoring', classname
 
761
    continue
 
762
 
 
763
  if clazz.get('template_typename'):
 
764
    print 'zz ignoring templated base class', classname
 
765
    continue
 
766
 
 
767
  # Nothing to generate for pure virtual classes XXX actually this is not so. We do need to generate wrappers for returned objects,
 
768
  # they are of a concrete class of course, but an known one, so we create a wrapper for an abstract base class.
 
769
 
 
770
  possible_prefix = (classname.split('::')[0] + '::') if '::' in classname else ''
 
771
 
 
772
  def check_pure_virtual(clazz, progeny):
 
773
    #if not clazz.get('inherits'): return False # If no inheritance info, not a class, this is a CppHeaderParser struct
 
774
    print 'Checking pure virtual for', clazz['name'], clazz['inherits']
 
775
    # If we do not recognize any of the parent classes, assume this is pure virtual - ignore it
 
776
    parents = [parent['class'] for parent in clazz['inherits']]
 
777
    parents = [parent.split('<')[0] for parent in parents] # remove template stuff
 
778
    parents = [parent if parent in classes else possible_prefix + parent for parent in parents]
 
779
    if any([not parent in classes for parent in parents]):
 
780
      print 'zz Warning: unknown parent class', parents, 'for', classname
 
781
      return True
 
782
    if any([check_pure_virtual(classes[parent], [clazz] + progeny) for parent in parents]): return True
 
783
 
 
784
    def dirtied(mname):
 
785
      #print 'zz checking dirtiness for', mname, 'in', progeny
 
786
      for progen in progeny:
 
787
        for method in progen['methods']:
 
788
          if method['name'] == mname and not method['pure_virtual']:
 
789
            #print 'zz dirty'
 
790
            return True
 
791
      #print 'zz not dirtied'
 
792
      return False
 
793
 
 
794
    for method in clazz['methods']:
 
795
      if method['pure_virtual'] and not dirtied(method['name']):
 
796
        print 'zz ignoring pure virtual class', classname, 'due to', method['name']
 
797
        return True
 
798
 
 
799
  clazz['abstract'] = check_pure_virtual(clazz, []) or clazz.get('effectively_abstract')
 
800
 
 
801
  print 'zz', classname, 'is abstract?', clazz['abstract']
 
802
  #if check_pure_virtual(clazz, []):
 
803
  #  continue
 
804
 
 
805
  # Add a constructor if none exist
 
806
  has_constructor = check_has_constructor(clazz)
 
807
 
 
808
  print 'zz', classname, 'has constructor?', has_constructor
 
809
 
 
810
  if not has_constructor:
 
811
    if not clazz['abstract']:
 
812
      print 'zz no constructor for', classname, 'and not abstract, so ignoring'
 
813
      continue
 
814
  
 
815
    #clazz['methods'] = [{
 
816
    #  'name': classname,
 
817
    #  'parameters': [],
 
818
    #  'pure_virtual': False,
 
819
    #  'destructor': False,
 
820
    #}] + clazz['methods']
 
821
 
 
822
  generate_class(classname, classname, clazz)
 
823
 
 
824
  # TODO: Add a destructor
 
825
 
 
826
# Finish up
 
827
 
 
828
gen_c.write('''
 
829
 
 
830
}
 
831
''')
 
832
 
 
833
gen_c.close()
 
834
gen_js.close()
 
835