| #!/usr/bin/env python |
| |
| # Copyright 2018 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import fidl |
| import json |
| |
| |
| class _CompoundIdentifier(object): |
| |
| def __init__(self, library, name): |
| self.library = library |
| self.name = name |
| |
| |
| def _ParseLibraryName(lib): |
| return lib.split('.') |
| |
| |
| def _ParseCompoundIdentifier(ident): |
| parts = ident.split('/', 2) |
| raw_library = '' |
| raw_name = parts[0] |
| if len(parts) == 2: |
| raw_library, raw_name = parts |
| library = _ParseLibraryName(raw_library) |
| return _CompoundIdentifier(library, raw_name) |
| |
| |
| def _ChangeIfReserved(name): |
| # TODO(crbug.com/883496): Remap any JS keywords. |
| return name |
| |
| |
| def _CompileCompoundIdentifier(compound, ext=''): |
| result = _ChangeIfReserved(compound.name) + ext |
| return result |
| |
| |
| def _CompileIdentifier(ident): |
| return _ChangeIfReserved(ident) |
| |
| |
| def _JsTypeForPrimitiveType(t): |
| mapping = { |
| fidl.IntegerType.INT16: 'number', |
| fidl.IntegerType.INT32: 'number', |
| fidl.IntegerType.INT64: 'BigInt', |
| fidl.IntegerType.INT8: 'number', |
| fidl.IntegerType.UINT16: 'number', |
| fidl.IntegerType.UINT32: 'number', |
| fidl.IntegerType.UINT64: 'BigInt', |
| fidl.IntegerType.UINT8: 'number', |
| } |
| return mapping[t] |
| |
| |
| def _InlineSizeOfType(t): |
| if t.kind == fidl.TypeKind.PRIMITIVE: |
| return { |
| 'int16': 2, |
| 'int32': 4, |
| 'int64': 8, |
| 'int8': 1, |
| 'uint16': 2, |
| 'uint32': 4, |
| 'uint64': 8, |
| 'uint8': 1, |
| }[t.subtype] |
| else: |
| raise NotImplementedError() |
| |
| |
| def _CompileConstant(val): |
| if val.kind == fidl.ConstantKind.IDENTIFIER: |
| raise NotImplementedError() |
| elif val.kind == fidl.ConstantKind.LITERAL: |
| return _CompileLiteral(val.literal) |
| else: |
| raise Exception('unexpected kind') |
| |
| |
| def _CompileLiteral(val): |
| if val.kind == fidl.LiteralKind.STRING: |
| # TODO(crbug.com/883496): This needs to encode the string in an escaped |
| # form suitable to JS. Currently using the escaped Python representation, |
| # which is passably compatible, but surely has differences in edge cases. |
| return repr(val.value) |
| elif val.kind == fidl.LiteralKind.NUMERIC: |
| return val.value |
| elif val.kind == fidl.LiteralKind.TRUE: |
| return 'true' |
| elif val.kind == fidl.LiteralKind.FALSE: |
| return 'false' |
| elif val.kind == fidl.LiteralKind.DEFAULT: |
| return 'default' |
| else: |
| raise Exception('unexpected kind') |
| |
| |
| class Compiler(object): |
| |
| def __init__(self, fidl, output_file): |
| self.fidl = fidl |
| self.f = output_file |
| self.output_deferred_to_eof = '' |
| self.type_table_defined = set() |
| |
| def Compile(self): |
| self._EmitHeader() |
| for c in self.fidl.const_declarations: |
| self._CompileConst(c) |
| for e in self.fidl.enum_declarations: |
| self._CompileEnum(e) |
| if self.fidl.union_declarations: |
| raise NotImplementedError() |
| for s in self.fidl.struct_declarations: |
| self._CompileStruct(s) |
| for i in self.fidl.interface_declarations: |
| self._CompileInterface(i) |
| |
| self.f.write(self.output_deferred_to_eof) |
| |
| def _EmitHeader(self): |
| self.f.write('''// Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // WARNING: This file is machine generated by fidlgen_js. |
| |
| ''') |
| |
| def _CompileConst(self, c): |
| self.f.write('''/** |
| * @const {%(type)s} |
| */ |
| const %(name)s = %(value)s; |
| ''' % c.to_dict()) |
| |
| def _CompileEnum(self, enum): |
| compound = _ParseCompoundIdentifier(enum.name) |
| name = _CompileCompoundIdentifier(compound) |
| js_type = _JsTypeForPrimitiveType(enum.type) |
| data = {'js_type': js_type, 'type': enum.type.value, 'name': name} |
| self.f.write('''/** |
| * @enum {%(js_type)s} |
| */ |
| const %(name)s = { |
| ''' % data) |
| for member in enum.members: |
| self.f.write( |
| ''' %s: %s,\n''' % (member.name, _CompileConstant(member.value))) |
| self.f.write('};\n') |
| self.f.write('const _kTT_%(name)s = _kTT_%(type)s;\n\n' % data) |
| |
| def _CompileStruct(self, struct): |
| compound = _ParseCompoundIdentifier(struct.name) |
| name = _CompileCompoundIdentifier(compound) |
| param_names = [_ChangeIfReserved(x.name) for x in struct.members] |
| # TODO(crbug.com/883496): @param and types. |
| self.f.write('''/** |
| * @constructor |
| * @struct |
| */ |
| function %(name)s(%(param_names)s) { |
| ''' % { |
| 'name': name, |
| 'param_names': ', '.join(param_names) |
| }) |
| for member in struct.members: |
| member_name = _ChangeIfReserved(member.name) |
| value = '%(member_name)s' |
| if member.maybe_default_value: |
| value = ('(%(member_name)s !== undefined) ? %(member_name)s : ' + |
| _CompileConstant(member.maybe_default_value)) |
| self.f.write((' this.%(member_name)s = ' + value + ';\n') % |
| {'member_name': member_name}) |
| self.f.write('}\n\n') |
| |
| self.f.write('''const _kTT_%(name)s = { |
| enc: function(e, o, v) { |
| ''' % {'name': name}) |
| |
| for member in struct.members: |
| element_ttname = self._CompileType(member.type) |
| self.f.write( |
| ' _kTT_%(element_ttname)s.enc(' |
| 'e, o + %(offset)s, v.%(member_name)s);\n' % { |
| 'element_ttname': element_ttname, |
| 'offset': member.offset, |
| 'member_name': _ChangeIfReserved(member.name) |
| }) |
| |
| self.f.write(''' }, |
| dec: function(d, o) { |
| ''') |
| |
| for member in struct.members: |
| element_ttname = self._CompileType(member.type) |
| self.f.write( |
| ' var $temp_%(member_name)s = _kTT_%(element_ttname)s.dec(' |
| 'd, o + %(offset)s);\n' % { |
| 'element_ttname': element_ttname, |
| 'offset': member.offset, |
| 'member_name': _ChangeIfReserved(member.name) |
| }) |
| self.f.write(''' return new %(name)s(%(temp_names)s); |
| } |
| }; |
| |
| ''' % { |
| 'name': name, |
| 'temp_names': ', '.join(['$temp_' + x for x in param_names]) |
| }) |
| |
| def _CompileType(self, t): |
| if t.kind == fidl.TypeKind.PRIMITIVE: |
| return t.subtype |
| elif t.kind == fidl.TypeKind.STRING: |
| return 'String' + ('_Nullable' if t.nullable else '_Nonnull') |
| elif t.kind == fidl.TypeKind.IDENTIFIER: |
| compound = _ParseCompoundIdentifier(t.identifier) |
| name = _CompileCompoundIdentifier(compound) |
| return name |
| elif t.kind == fidl.TypeKind.HANDLE: |
| return 'Handle' |
| elif t.kind == fidl.TypeKind.VECTOR: |
| element_ttname = self._CompileType(t.element_type) |
| ttname = ( |
| 'VEC_' + ('Nullable_' if t.nullable else 'Nonnull_') + element_ttname) |
| pointer_set = ''' if (v === null || v === undefined) { |
| e.data.setUint32(o + 8, 0, $fidl__kLE); |
| e.data.setUint32(o + 12, 0, $fidl__kLE); |
| } else { |
| e.data.setUint32(o + 8, 0xffffffff, $fidl__kLE); |
| e.data.setUint32(o + 12, 0xffffffff, $fidl__kLE); |
| }''' |
| if not t.nullable: |
| throw_if_null_enc = ('if (v === null || v === undefined) ' |
| 'throw "non-null vector required";') |
| throw_if_null_dec = ('if (pointer === 0) ' |
| 'throw "non-null vector required";') |
| pointer_set = ''' e.data.setUint32(o + 8, 0xffffffff, $fidl__kLE); |
| e.data.setUint32(o + 12, 0xffffffff, $fidl__kLE);''' |
| |
| if ttname not in self.type_table_defined: |
| self.type_table_defined.add(ttname) |
| self.output_deferred_to_eof += ('''\ |
| const _kTT_%(ttname)s = { |
| enc: function(e, o, v) { |
| %(throw_if_null_enc)s |
| e.data.setUint32(o, v.length, $fidl__kLE); |
| e.data.setUint32(o + 4, 0, $fidl__kLE); |
| %(pointer_set)s |
| e.outOfLine.push([function(e, body) { |
| var start = e.alloc(body.length * %(element_size)s); |
| for (var i = 0; i < body.length; i++) { |
| _kTT_%(element_ttname)s.enc(e, start + (i * %(element_size)s), body[i]); |
| } |
| }, |
| v]); |
| }, |
| dec: function(d, o) { |
| var len = d.data.getUint32(o, $fidl__kLE); |
| var pointer = d.data.getUint32(o + 8, $fidl__kLE); |
| %(throw_if_null_dec)s |
| var dataOffset = d.claimMemory(len * %(element_size)s); |
| var result = []; |
| for (var i = 0; i < len; i++) { |
| result.push(_kTT_%(element_ttname)s.dec( |
| d, dataOffset + (i * %(element_size)s))); |
| } |
| return result; |
| } |
| }; |
| |
| ''' % { |
| 'ttname': ttname, |
| 'element_ttname': element_ttname, |
| 'element_size': _InlineSizeOfType(t.element_type), |
| 'pointer_set': pointer_set, |
| 'throw_if_null_enc': throw_if_null_enc, |
| 'throw_if_null_dec': throw_if_null_dec |
| }) |
| return ttname |
| else: |
| raise NotImplementedError() |
| |
| def _GenerateJsInterfaceForInterface(self, name, interface): |
| """Generates a JS @interface for the given FIDL interface.""" |
| self.f.write('''/** |
| * @interface |
| */ |
| function %(name)s() {} |
| |
| ''' % {'name': name}) |
| |
| # Define a JS interface part for the interface for typechecking. |
| for method in interface.methods: |
| method_name = _CompileIdentifier(method.name) |
| if method.has_request: |
| param_names = [_CompileIdentifier(x.name) for x in method.maybe_request] |
| if len(param_names): |
| self.f.write('/**\n') |
| # TODO(crbug.com/883496): Emit @param and @return type comments. |
| self.f.write(' */\n') |
| self.f.write( |
| '%(name)s.prototype.%(method_name)s = ' |
| 'function(%(param_names)s) {};\n\n' % { |
| 'name': name, |
| 'method_name': method_name, |
| 'param_names': ', '.join(param_names) |
| }) |
| |
| # Emit message ordinals for later use. |
| for method in interface.methods: |
| method_name = _CompileIdentifier(method.name) |
| self.f.write( |
| 'const _k%(name)s_%(method_name)s_Ordinal = %(ordinal)s;\n' % { |
| 'name': name, |
| 'method_name': method_name, |
| 'ordinal': method.ordinal |
| }) |
| |
| self.f.write('\n') |
| |
| def _GenerateJsProxyForInterface(self, name, interface): |
| """Generates the JS side implementation of a proxy class implementing the |
| given interface.""" |
| proxy_name = name + 'Proxy' |
| self.f.write('''/** |
| * @constructor |
| * @implements %(name)s |
| */ |
| function %(proxy_name)s() { |
| this.channel = $ZX_HANDLE_INVALID; |
| } |
| |
| %(proxy_name)s.prototype.$bind = function(channel) { |
| this.channel = channel; |
| }; |
| |
| ''' % { |
| 'name': name, |
| 'proxy_name': proxy_name |
| }) |
| for method in interface.methods: |
| method_name = _CompileIdentifier(method.name) |
| if method.has_request: |
| type_tables = [] |
| for param in method.maybe_request: |
| type_tables.append(self._CompileType(param.type)) |
| param_names = [_CompileIdentifier(x.name) for x in method.maybe_request] |
| self.f.write( |
| '''\ |
| %(proxy_name)s.prototype.%(method_name)s = function(%(param_names)s) { |
| if (this.channel === $ZX_HANDLE_INVALID) { |
| throw "channel closed"; |
| } |
| var $encoder = new $fidl_Encoder(_k%(name)s_%(method_name)s_Ordinal); |
| $encoder.alloc(%(size)s - $fidl_kMessageHeaderSize); |
| ''' % { |
| 'name': name, |
| 'proxy_name': proxy_name, |
| 'method_name': method_name, |
| 'param_names': ', '.join(param_names), |
| 'size': method.maybe_request_size |
| }) |
| |
| for param, ttname in zip(method.maybe_request, type_tables): |
| self.f.write( |
| '''\ |
| _kTT_%(type_table)s.enc($encoder, %(offset)s, %(param_name)s); |
| ''' % { |
| 'type_table': ttname, |
| 'param_name': _CompileIdentifier(param.name), |
| 'offset': param.offset |
| }) |
| |
| self.f.write(''' var $writeResult = $ZxChannelWrite(this.channel, |
| $encoder.messageData(), |
| $encoder.messageHandles()); |
| if ($writeResult !== $ZX_OK) { |
| throw "$ZxChannelWrite failed: " + $writeResult; |
| } |
| ''') |
| |
| if method.has_response: |
| type_tables = [] |
| for param in method.maybe_response: |
| type_tables.append(self._CompileType(param.type)) |
| self.f.write(''' |
| return $ZxObjectWaitOne(this.channel, $ZX_CHANNEL_READABLE, $ZX_TIME_INFINITE) |
| .then(() => new Promise(res => { |
| var $readResult = $ZxChannelRead(this.channel); |
| if ($readResult.status !== $ZX_OK) { |
| throw "channel read failed"; |
| } |
| |
| var $view = new DataView($readResult.data); |
| |
| var $decoder = new $fidl_Decoder($view, $readResult.handles); |
| $decoder.claimMemory(%(size)s - $fidl_kMessageHeaderSize); |
| ''' % {'size': method.maybe_response_size}) |
| for param, ttname in zip(method.maybe_response, type_tables): |
| self.f.write( |
| '''\ |
| var %(param_name)s = _kTT_%(type_table)s.dec($decoder, %(offset)s); |
| ''' % { |
| 'type_table': ttname, |
| 'param_name': _CompileIdentifier(param.name), |
| 'offset': param.offset |
| }) |
| |
| self.f.write(''' |
| res(%(args)s); |
| })); |
| ''' % {'args': ', '.join(x.name for x in method.maybe_response)}) |
| |
| self.f.write('''}; |
| |
| ''') |
| |
| def _CompileInterface(self, interface): |
| compound = _ParseCompoundIdentifier(interface.name) |
| name = _CompileCompoundIdentifier(compound) |
| self._GenerateJsInterfaceForInterface(name, interface) |
| self._GenerateJsProxyForInterface(name, interface) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('json') |
| parser.add_argument('--output', required=True) |
| args = parser.parse_args() |
| |
| fidl_obj = fidl.fidl_from_dict(json.load(open(args.json, 'r'))) |
| with open(args.output, 'w') as f: |
| c = Compiler(fidl_obj, f) |
| c.Compile() |
| |
| |
| if __name__ == '__main__': |
| main() |