blob: 3d3aae14faadf1b2a27dfbd20596f76ba646f095 [file] [log] [blame]
#!/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()
if self.fidl.struct_declarations:
raise NotImplementedError()
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 _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 type comments here.
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 $result = zx.channelWrite(this.channel,
$encoder.messageData(),
$encoder.messageHandles());
if ($result !== zx.ZX_OK) {
throw "zx.channelWrite failed: " + $result;
}
};
''')
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()