| #!/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 |
| from fidl import * |
| 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 = { |
| IntegerType.INT16: 'number', |
| IntegerType.INT32: 'number', |
| IntegerType.INT64: 'BigInt', |
| IntegerType.INT8: 'number', |
| IntegerType.UINT16: 'number', |
| IntegerType.UINT32: 'number', |
| IntegerType.UINT64: 'BigInt', |
| IntegerType.UINT8: 'number', |
| } |
| return mapping[t] |
| |
| |
| def _InlineSizeOfType(t): |
| if t.kind == 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 == ConstantKind.IDENTIFIER: |
| raise NotImplementedError() |
| elif val.kind == ConstantKind.LITERAL: |
| return _CompileLiteral(val.literal) |
| else: |
| raise Exception('unexpected kind') |
| |
| |
| def _CompileLiteral(val): |
| if val.kind == 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 == LiteralKind.NUMERIC: |
| return val.value |
| elif val.kind == LiteralKind.TRUE: |
| return 'true' |
| elif val.kind == LiteralKind.FALSE: |
| return 'false' |
| elif val.kind == 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 |
| |
| 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) |
| |
| 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(' |
| 'e, o + %(offset)s);\n' % { |
| 'element_ttname': element_ttname, |
| 'offset': member.offset, |
| 'member_name': _ChangeIfReserved(member.name) |
| }) |
| self.f.write(''' return %(name)s(%(temp_names)s); |
| } |
| }; |
| |
| ''' % { 'name': name, |
| 'temp_names': ', '.join(['$temp_' + x for x in param_names]) }) |
| |
| |
| def _CompileType(self, t): |
| if t.kind == TypeKind.PRIMITIVE: |
| return t.subtype |
| elif t.kind == TypeKind.STRING: |
| return 'String' + ('_Nullable' if t.nullable else '_Nonnull') |
| elif t.kind == TypeKind.IDENTIFIER: |
| compound = _ParseCompoundIdentifier(t.identifier) |
| name = _CompileCompoundIdentifier(compound) |
| return name |
| elif t.kind == TypeKind.VECTOR: |
| element_ttname = self._CompileType(t.element_type) |
| ttname = ('VEC_' + ('Nullable_' if t.nullable else 'Nonnull_') + |
| element_ttname) |
| throw_if_null = '/* v may be null */' |
| 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 = ('if (v === null || v === undefined) ' |
| 'throw "non-null vector required";') |
| pointer_set = ''' e.data.setUint32(o + 8, 0xffffffff, $fidl__kLE); |
| e.data.setUint32(o + 12, 0xffffffff, $fidl__kLE);''' |
| |
| self.f.write( |
| '''const _kTT_%(ttname)s = { |
| enc: function(e, o, v) { |
| %(throw_if_null)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]); |
| }, |
| }; |
| |
| ''' % { 'ttname': ttname, |
| 'element_ttname': element_ttname, |
| 'element_size': _InlineSizeOfType(t.element_type), |
| 'pointer_set': pointer_set, |
| 'throw_if_null': throw_if_null }) |
| 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.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.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 = zx.channelWrite(this.channel, |
| $encoder.messageData(), |
| $encoder.messageHandles()); |
| if ($writeResult !== zx.ZX_OK) { |
| throw "zx.channelWrite failed: " + $writeResult; |
| } |
| ''') |
| |
| if method.has_response: |
| self.f.write(''' |
| return zx |
| .objectWaitOne(this.channel, zx.ZX_CHANNEL_READABLE, zx.ZX_TIME_INFINITE) |
| .then(() => new Promise(res => { |
| var $readResult = zx.channelRead(this.channel); |
| if ($readResult.status !== zx.ZX_OK) { |
| throw "channel read failed"; |
| } |
| |
| var $view = new DataView($readResult.data); |
| |
| var $decoder = new $fidl_Decoder($view, []); |
| $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 = fidl_from_dict(json.load(open(args.json, 'r'))) |
| with open(args.output, 'w') as f: |
| c = Compiler(fidl, f) |
| c.Compile() |
| |
| |
| if __name__ == '__main__': |
| main() |