| LEFT | RIGHT | 
|    1 #!/usr/bin/env python |    1 #!/usr/bin/env python | 
|    2  |    2  | 
|    3 import json |    3 import json | 
|    4 import os |    4 import os | 
|    5 import re |    5 import re | 
|    6 import subprocess |    6 import subprocess | 
|    7 import warnings |    7 import warnings | 
|    8  |    8  | 
|    9 EMSCRIPTEN_PATH = '../emscripten' |    9 EMSCRIPTEN_PATH = '../emscripten' | 
|   10 SOURCE_DIR = './compiled' |   10 SOURCE_DIR = './compiled' | 
|   11 SOURCE_FILES = [ |   11 SOURCE_FILES = [ | 
|   12   os.path.join(SOURCE_DIR, f) |   12   os.path.join(SOURCE_DIR, f) | 
|   13   for f in os.listdir(SOURCE_DIR) |   13   for f in os.listdir(SOURCE_DIR) | 
|   14   if f.endswith('.cpp') |   14   if f.endswith('.cpp') | 
|   15 ] |   15 ] | 
|   16 API_FILE = os.path.join(SOURCE_DIR, 'api.cpp') |   16 BINDINGS_FILE = os.path.join(SOURCE_DIR, 'bindings.cpp') | 
|   17 API_OUTPUT = os.path.join(SOURCE_DIR, 'api.js') |   17 BINDINGS_GENERATOR = os.path.join(SOURCE_DIR, 'bindings.cpp.js') | 
 |   18 BINDINGS_OUTPUT = os.path.join(SOURCE_DIR, 'bindings.js') | 
|   18 COMPILER_OUTPUT = './lib/compiled.js' |   19 COMPILER_OUTPUT = './lib/compiled.js' | 
|   19 GENERATION_PARAMS = { |   20 GENERATION_PARAMS = { | 
|   20   'SHELL_FILE': "'%s'" % os.path.abspath(os.path.join(SOURCE_DIR, 'shell.js')), |   21   'SHELL_FILE': "'%s'" % os.path.abspath(os.path.join(SOURCE_DIR, 'shell.js')), | 
 |   22   'ASM_JS': 2,  # "almost asm" | 
|   21   'TOTAL_MEMORY': 16*1024*1024, |   23   'TOTAL_MEMORY': 16*1024*1024, | 
|   22   'TOTAL_STACK': 1*1024*1024, |   24   'TOTAL_STACK': 1*1024*1024, | 
|   23   'ALLOW_MEMORY_GROWTH': 1, |   25   'ALLOW_MEMORY_GROWTH': 1, | 
|   24   'NO_EXIT_RUNTIME': 1, |   26   'NO_EXIT_RUNTIME': 1, | 
|   25   'DISABLE_EXCEPTION_CATCHING': 0, |  | 
|   26   'NO_DYNAMIC_EXECUTION': 1, |   27   'NO_DYNAMIC_EXECUTION': 1, | 
|   27   'NO_BROWSER': 1, |   28   'NO_BROWSER': 1, | 
|   28   'NO_FILESYSTEM': 1, |   29   'NO_FILESYSTEM': 1, | 
|   29   'INVOKE_RUN': 0, |   30   'INVOKE_RUN': 0, | 
|   30   'NODE_STDOUT_FLUSH_WORKAROUND': 0, |   31   'NODE_STDOUT_FLUSH_WORKAROUND': 0, | 
|   31 } |   32 } | 
|   32 DEFINES = ['DEBUG'] |   33 DEFINES = [] | 
|   33 ADDITIONAL_PARAMS = ['-O3', '-m32', '-std=gnu++11', '--memory-init-file', '0', |   34 ADDITIONAL_PARAMS = ['-O3', '-m32', '-std=gnu++14', '--memory-init-file', '0', | 
|   34     '--emit-symbol-map'] |   35     '--emit-symbol-map'] | 
|   35  |   36  | 
|   36 def getenv(): |   37 def getenv(): | 
|   37   path = [] |   38   path = [] | 
|   38   env = {} |   39   env = {} | 
|   39   output = subprocess.check_output([ |   40   output = subprocess.check_output([ | 
|   40       '/bin/sh', '-c', os.path.join(EMSCRIPTEN_PATH, 'emsdk_env.sh')]) |   41       '/bin/bash', '-c', os.path.join(EMSCRIPTEN_PATH, 'emsdk_env.sh')]) | 
|   41   for line in output.splitlines(): |   42   for line in output.splitlines(): | 
|   42     match = re.search(r'^\s*PATH\s*\+=\s*(.*)', line) |   43     match = re.search(r'^\s*PATH\s*\+=\s*(.*)', line) | 
|   43     if match: |   44     if match: | 
|   44       path.append(match.group(1)) |   45       path.append(match.group(1)) | 
|   45     match = re.search(r'^\s*(\w+)\s*=\s*(.*)', line) |   46     match = re.search(r'^\s*(\w+)\s*=\s*(.*)', line) | 
|   46     if match: |   47     if match: | 
|   47       env[match.group(1)] = match.group(2) |   48       env[match.group(1)] = match.group(2) | 
|   48   env['PATH'] = ':'.join([os.environ['PATH']] + path) |   49   env['PATH'] = os.pathsep.join([os.environ['PATH']] + path) | 
|   49   return env |   50   return env | 
|   50  |   51  | 
|   51 def generate_api(env): |   52 def generate_bindings(env): | 
|   52   params = [os.path.join(env['EMSCRIPTEN'], 'emcc'), '-E', API_FILE] |   53   params = [os.path.join(env['EMSCRIPTEN'], 'emcc'), BINDINGS_FILE, | 
|   53   params.extend(ADDITIONAL_PARAMS) |   54     '-o', BINDINGS_GENERATOR, '-std=gnu++14', '-DPRINT_BINDINGS', | 
|   54   output = subprocess.check_output(params, env=env) |   55     '-s', 'WARN_ON_UNDEFINED_SYMBOLS=0', | 
 |   56   ] | 
 |   57   subprocess.check_call(params, env=env) | 
|   55  |   58  | 
|   56   differentiators = {} |   59   node = subprocess.check_output('which node', env=env, shell=True).strip(); | 
|   57   differentiator = None |   60   with open(BINDINGS_OUTPUT, 'w') as file: | 
|   58  |   61     subprocess.check_call([node, BINDINGS_GENERATOR], env=env, stdout=file) | 
|   59   cls = None |  | 
|   60   method = None |  | 
|   61  |  | 
|   62   def wrap_call(func, is_instance, result_type, string_args, arg_count=10): |  | 
|   63     params = ['arg%i' % i for i in range(arg_count)] |  | 
|   64     prefix = '''\ |  | 
|   65 function(%s) |  | 
|   66 { |  | 
|   67 ''' % ', '.join(params) |  | 
|   68     suffix = '''\ |  | 
|   69   return result; |  | 
|   70 }''' |  | 
|   71  |  | 
|   72     if result_type == 'string' or len(string_args): |  | 
|   73       prefix += '  var sp = Runtime.stackSave();\n' |  | 
|   74       suffix = '  Runtime.stackRestore(sp);\n' + suffix |  | 
|   75  |  | 
|   76     for pos in string_args: |  | 
|   77       params[pos] = 'createString(%s)' % params[pos] |  | 
|   78  |  | 
|   79     if is_instance: |  | 
|   80       params.insert(0, 'this._pointer') |  | 
|   81  |  | 
|   82     if result_type == 'primitive': |  | 
|   83       prefix += '  var result = _%s(%s);\n' % (func, ', '.join(params)) |  | 
|   84     elif result_type == 'string': |  | 
|   85       prefix += '  var result = createString();\n' |  | 
|   86       params.insert(0, 'result') |  | 
|   87       prefix += '  _%s(%s);\n' % (func, ', '.join(params)) |  | 
|   88       prefix += '  result = getStringData(result);\n' |  | 
|   89     else: |  | 
|   90       prefix += '  var pointer = _%s(%s);\n' % (func, ', '.join(params)) |  | 
|   91       if result_type in differentiators: |  | 
|   92         prefix += '  var type = _%s(pointer);\n' % differentiator['func'] |  | 
|   93         prefix += '  if (type in %s_mapping)\n' % result_type |  | 
|   94         prefix += '    var result = new (exports[%s_mapping[type]])(pointer);\n'
      % result_type |  | 
|   95         prefix += '  else\n' |  | 
|   96         prefix += '    throw new Error("Unexpected %s type: " + type);\n' % resu
     lt_type |  | 
|   97       else: |  | 
|   98         prefix += '  var result = %s(pointer);\n' % result_type |  | 
|   99  |  | 
|  100     return prefix + suffix |  | 
|  101  |  | 
|  102   def property_descriptor(property): |  | 
|  103     if property['type'] == 'static': |  | 
|  104       return 'value: %s' % property['getter'] |  | 
|  105  |  | 
|  106     result = 'get: %s' % wrap_call( |  | 
|  107       property['getter'], is_instance=True, result_type=property['type'], |  | 
|  108       string_args=[], arg_count=0 |  | 
|  109     ) |  | 
|  110     if property['setter'] is not None: |  | 
|  111       result += ', set: %s' % wrap_call( |  | 
|  112         property['setter'], is_instance=True, result_type='primitive', |  | 
|  113         string_args=[0] if property['type'] == 'string' else [], |  | 
|  114         arg_count=1 |  | 
|  115       ) |  | 
|  116     return result |  | 
|  117  |  | 
|  118   def method_descriptor(method): |  | 
|  119     return wrap_call( |  | 
|  120       method['func'], is_instance=(method['type'] == 'instance'), |  | 
|  121       result_type=method['result'], string_args=method['string_args'] |  | 
|  122     ) |  | 
|  123  |  | 
|  124   def write_class(file, cls): |  | 
|  125     name = cls['name'] |  | 
|  126     if name in differentiators: |  | 
|  127       print >>file, 'var %s_mapping = %s;' % ( |  | 
|  128           name, json.dumps(differentiators[name]['mapping'], sort_keys=True)) |  | 
|  129  |  | 
|  130     print >>file, 'exports.%s = createClass(%s);' % ( |  | 
|  131         name, 'exports.' + cls['superclass'] if cls['superclass'] else '') |  | 
|  132     for property in cls['properties']: |  | 
|  133       print >>file, 'Object.defineProperty(exports.%s.prototype, "%s", {%s});' %
      ( |  | 
|  134           name, property['name'], property_descriptor(property)) |  | 
|  135     for method in cls['methods']: |  | 
|  136       obj = ('exports.%s' if method['type'] == 'class' else 'exports.%s.prototyp
     e') % name |  | 
|  137       print >>file, '%s.%s = %s;' % ( |  | 
|  138           obj, method['name'], method_descriptor(method)) |  | 
|  139     for initializer in cls['class_initializers']: |  | 
|  140       print >>file, '_%s();' % initializer |  | 
|  141  |  | 
|  142   def handle_class(name): |  | 
|  143     if cls is not None: |  | 
|  144       write_class(file, cls) |  | 
|  145     differentiator = None |  | 
|  146     return { |  | 
|  147       'name': command[1], |  | 
|  148       'superclass': None, |  | 
|  149       'properties': [], |  | 
|  150       'methods': [], |  | 
|  151       'class_initializers': [], |  | 
|  152     } |  | 
|  153  |  | 
|  154   def handle_superclass(name): |  | 
|  155     if cls is None: |  | 
|  156       warnings.warn('Superclass declared outside a class: ' + name) |  | 
|  157       return |  | 
|  158     differentiator = None |  | 
|  159     cls['superclass'] = name |  | 
|  160  |  | 
|  161   def handle_class_init(func): |  | 
|  162     if cls is None: |  | 
|  163       warnings.warn('Class initializer declared outside a class: ' + func) |  | 
|  164       return |  | 
|  165     differentiator = None |  | 
|  166     method = None |  | 
|  167     cls['class_initializers'].append(func) |  | 
|  168  |  | 
|  169   def handle_differentiator(cls, func): |  | 
|  170     differentiator = {'func': func, 'mapping': {}} |  | 
|  171     differentiators[cls] = differentiator |  | 
|  172     return differentiator |  | 
|  173  |  | 
|  174   def handle_differentiator_mapping(value, subclass): |  | 
|  175     if differentiator is None: |  | 
|  176       warnings.warn('Differentiator mapping declared without a differentiator: '
      + subclass) |  | 
|  177       return |  | 
|  178     differentiator['mapping'][value] = subclass |  | 
|  179  |  | 
|  180   def handle_property(type, name, getter, setter): |  | 
|  181     if cls is None: |  | 
|  182       warnings.warn('Property declared outside a class: ' + name) |  | 
|  183       return |  | 
|  184     method = None |  | 
|  185     differentiator = None |  | 
|  186     cls['properties'].append({ |  | 
|  187       'type': type, |  | 
|  188       'name': name, |  | 
|  189       'getter': getter, |  | 
|  190       'setter': setter, |  | 
|  191     }) |  | 
|  192  |  | 
|  193   def handle_method(type, name, func): |  | 
|  194     if cls is None: |  | 
|  195       warnings.warn('Method declared outside a class: ' + name) |  | 
|  196       return |  | 
|  197     differentiator = None |  | 
|  198     method = { |  | 
|  199       'type': type, |  | 
|  200       'name': name, |  | 
|  201       'func': func, |  | 
|  202       'result': 'primitive', |  | 
|  203       'string_args': [], |  | 
|  204     } |  | 
|  205     cls['methods'].append(method) |  | 
|  206     return method |  | 
|  207  |  | 
|  208   def handle_method_result(type): |  | 
|  209     if method is None: |  | 
|  210       warnings.warn('Method result declared without a method definition') |  | 
|  211       return |  | 
|  212     method['result'] = type |  | 
|  213  |  | 
|  214   def handle_string_arg(pos): |  | 
|  215     if method is None: |  | 
|  216       warnings.warn('Argument type declared without a method definition') |  | 
|  217       return |  | 
|  218     method['string_args'].append(int(pos)) |  | 
|  219  |  | 
|  220   with open(API_OUTPUT, 'w') as file: |  | 
|  221     for line in output.splitlines(): |  | 
|  222       match = re.search(r'#pragma\s+comment\((.+?)\)', line) |  | 
|  223       if match: |  | 
|  224         command = match.group(1).strip().split() |  | 
|  225         if command[0] == 'class': |  | 
|  226           cls = handle_class(command[1]) |  | 
|  227         elif command[0] == 'augments': |  | 
|  228           handle_superclass(command[1]) |  | 
|  229         elif command[0] == 'class_init': |  | 
|  230           handle_class_init(command[1]) |  | 
|  231         elif command[0] == 'differentiator': |  | 
|  232           differentiator = handle_differentiator(command[1], command[2]) |  | 
|  233         elif command[0] == 'differentiator_mapping': |  | 
|  234           handle_differentiator_mapping(command[1], command[2]) |  | 
|  235         elif command[0] == 'property': |  | 
|  236           handle_property('primitive', command[1], command[2], command[3] if len
     (command) > 3 else None) |  | 
|  237         elif command[0] == 'string_property': |  | 
|  238           handle_property('string', command[1], command[2], command[3] if len(co
     mmand) > 3 else None) |  | 
|  239         elif command[0] == 'static_property': |  | 
|  240           handle_property('static', command[1], command[2], None) |  | 
|  241         elif command[0] == 'method': |  | 
|  242           method = handle_method('instance', command[1], command[2]) |  | 
|  243         elif command[0] == 'class_method': |  | 
|  244           method = handle_method('class', command[1], command[2]) |  | 
|  245         elif command[0] == 'string_result': |  | 
|  246           handle_method_result('string') |  | 
|  247         elif command[0] == 'pointer_result': |  | 
|  248           handle_method_result(command[1]) |  | 
|  249         elif command[0] == 'string_arg': |  | 
|  250           handle_string_arg(command[1]) |  | 
|  251         else: |  | 
|  252           warnings.warn('Unknown declaration: ' + str(command)) |  | 
|  253  |  | 
|  254     if cls is not None: |  | 
|  255       write_class(file, cls) |  | 
|  256  |   62  | 
|  257 def run_compiler(env): |   63 def run_compiler(env): | 
|  258   params = [ |   64   params = [ | 
|  259     os.path.join(env['EMSCRIPTEN'], 'emcc'), |   65     os.path.join(env['EMSCRIPTEN'], 'emcc'), | 
|  260     '-o', COMPILER_OUTPUT, |   66     '-o', COMPILER_OUTPUT, | 
|  261     '--post-js', API_OUTPUT, |   67     '--post-js', BINDINGS_OUTPUT, | 
|  262   ] |   68   ] | 
|  263   params.extend(SOURCE_FILES) |   69   params.extend(SOURCE_FILES) | 
|  264   params.extend('-D' + flag for flag in DEFINES) |   70   params.extend('-D' + flag for flag in DEFINES) | 
|  265   for key, value in GENERATION_PARAMS.iteritems(): |   71   for key, value in GENERATION_PARAMS.iteritems(): | 
|  266     params.extend(['-s', '%s=%s' % (key, str(value))]) |   72     params.extend(['-s', '%s=%s' % (key, str(value))]) | 
|  267   params.extend(ADDITIONAL_PARAMS) |   73   params.extend(ADDITIONAL_PARAMS) | 
|  268   subprocess.check_call(params, env=env) |   74   subprocess.check_call(params, env=env) | 
|  269  |   75  | 
|  270 if __name__ == '__main__': |   76 if __name__ == '__main__': | 
|  271   env = getenv() |   77   env = getenv() | 
|  272   generate_api(env) |   78   generate_bindings(env) | 
|  273   run_compiler(env) |   79   run_compiler(env) | 
| LEFT | RIGHT |