blob: 484440e2d1cea1c6d65e5d33237acc072601c045 [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
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 _GetUnderlyingPrimitiveType(t):
"""Returns the underlying FIDL primitive type for a higher level type."""
if t.kind == fidl.TypeKind.PRIMITIVE:
return t.subtype
elif t.kind == fidl.TypeKind.STRING:
return 'string'
elif t.kind == fidl.TypeKind.IDENTIFIER:
# No underlying type is required because it will be implied by the type of
# the value that the identifer represents.
return None
else:
raise Exception(
'expected primitive or identifier representing primitive underlying '
'type, but got ' + str(t.kind))
def _InlineSizeOfPrimitiveType(primitive_type):
return {
'bool': 1,
'float32': 4,
'float64': 8,
'int16': 2,
'int32': 4,
'int64': 8,
'int8': 1,
'uint16': 2,
'uint32': 4,
'uint64': 8,
'uint8': 1,
}[primitive_type]
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 _BuildInlineSizeTable(fidl):
"""Builds a mapping from type name to inline type size. These need to be
extracted beforehand because a vector<X> can be required during compilation
before seeing the compilation of X."""
result = {}
for enum in fidl.enum_declarations:
result[enum.name] = _InlineSizeOfPrimitiveType(enum.type.value)
for union in fidl.union_declarations:
result[union.name] = union.size
for struct in fidl.struct_declarations:
result[struct.name] = struct.size
return result
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()
self.type_inline_size_by_name = _BuildInlineSizeTable(self.fidl)
# Used to hold the JS name for constants and enumerants. In particular,
# enums aren't scoped by name to their enum in the fidl json, but the JS
# bindings emit them as Enum.Something. So this maps from Something ->
# Enum.Something.
self.resolved_constant_name = {}
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)
for u in self.fidl.union_declarations:
self._CompileUnion(u)
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 _InlineSizeOfType(self, t):
if t.kind == fidl.TypeKind.PRIMITIVE:
return _InlineSizeOfPrimitiveType(t.subtype)
elif t.kind == fidl.TypeKind.STRING:
return 16
elif t.kind == fidl.TypeKind.IDENTIFIER:
size = self.type_inline_size_by_name.get(t.identifier)
if size is None:
raise Exception('expected ' + t.identifier +
' to be in self.type_inline_size_by_name')
return size
elif t.kind == fidl.TypeKind.HANDLE:
return 4
else:
raise NotImplementedError(t.kind)
def _CompileConstant(self, val, primitive_type):
"""primitive_type is the string representation of the underlying FIDL type
of the constant's value. Note that this is not a type object, but rather
the string name of a basic primitive type, e.g. 'int8' or 'uint64'."""
if val.kind == fidl.ConstantKind.IDENTIFIER:
js_name = self.resolved_constant_name.get(val.identifier)
if not js_name:
raise Exception('expected ' + val.identifer +
' to be in self.resolved_constant_name')
return js_name
elif val.kind == fidl.ConstantKind.LITERAL:
lit_kind = val.literal.kind
if lit_kind == fidl.LiteralKind.STRING:
return json.dumps(val.literal.value)
elif lit_kind == fidl.LiteralKind.NUMERIC:
suffix = 'n' if primitive_type in ('int64', 'uint64') else ''
return val.literal.value + suffix
elif lit_kind == fidl.LiteralKind.TRUE:
return 'true'
elif lit_kind == fidl.LiteralKind.FALSE:
return 'false'
elif lit_kind == fidl.LiteralKind.DEFAULT:
return 'default'
else:
raise Exception('unexpected kind')
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, const):
compound = _ParseCompoundIdentifier(const.name)
name = _CompileCompoundIdentifier(compound)
value = self._CompileConstant(const.value,
_GetUnderlyingPrimitiveType(const.type))
self.f.write('''/**
* @const
*/
const %(name)s = %(value)s;
''' % {
'name': name,
'value': value
})
self.resolved_constant_name[const.name] = name
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:
# The 'type' of an enum isn't a real Type like most other places, but
# instead just a simple 'int8' or similar.
underlying_type = enum.type.value
self.f.write(
''' %s: %s,\n''' %
(member.name, self._CompileConstant(member.value, underlying_type)))
fidl_constant_name = '.'.join(compound.library) + '/' + member.name
javascript_name = name + '.' + member.name
self.resolved_constant_name[fidl_constant_name] = javascript_name
self.f.write('};\n')
self.f.write('const _kTT_%(name)s = _kTT_%(type)s;\n\n' % data)
def _CompileUnion(self, union):
compound = _ParseCompoundIdentifier(union.name)
name = _CompileCompoundIdentifier(compound)
member_names = []
enc_cases = []
dec_cases = []
for i, m in enumerate(union.members):
member_name = _ChangeIfReserved(m.name)
member_names.append(member_name)
member_type = self._CompileType(m.type)
enc_cases.append('''\
case %(index)s:
_kTT_%(member_type)s.enc(e, o + 4, v.%(member_name)s);
break;''' % {
'index': i,
'member_type': member_type,
'member_name': member_name,
})
dec_cases.append('''\
case %(index)s:
result.set_%(member_name)s(_kTT_%(member_type)s.dec(d, o + 4));
break;''' % {
'index': i,
'member_type': member_type,
'member_name': member_name,
})
self.f.write(
'''\
const _kTT_%(name)s = {
enc: function(e, o, v) {
if (v.$tag === $fidl__kInvalidUnionTag) throw "invalid tag";
e.data.setUint32(o, v.$tag, $fidl__kLE);
switch (v.$tag) {
%(enc_cases)s
}
},
dec: function(d, o) {
var tag = d.data.getUint32(o, $fidl__kLE);
var result = new %(name)s();
switch (tag) {
%(dec_cases)s
default:
throw "invalid tag";
}
return result;
},
};
const _kTT_%(name)s_Nullable = {
enc: function(e, o, v) {
e.data.setUint32(o, v ? 0xffffffff : 0, $fidl__kLE);
e.data.setUint32(o + 4, v ? 0xffffffff : 0, $fidl__kLE);
var start = e.alloc(%(size)s);
_kTT_%(name)s.enc(e, start, v);
},
dec: function(d, o) {
if (d.data.getUint32(o, $fidl__kLE) === 0) {
return new %(name)s();
}
var pointer = d.data.getUint32(o + 4, $fidl__kLE);
var dataOffset = d.claimMemory(%(size)s);
return _kTT_%(name)s.dec(d, dataOffset);
},
};
/**
* @constructor
*/
function %(name)s() { this.reset(); }
%(name)s.prototype.reset = function(i) {
this.$tag = (i === undefined) ? $fidl__kInvalidUnionTag : i;
''' % {
'name': name,
'size': union.size,
'enc_cases': '\n'.join(enc_cases),
'dec_cases': '\n'.join(dec_cases),
})
for m in member_names:
self.f.write(' this.%s = null;\n' % m)
self.f.write('}\n\n')
for i, m in enumerate(member_names):
self.f.write('''\
%(name)s.prototype.set_%(member_name)s = function(v) {
this.reset(%(index)s);
this.%(member_name)s = v;
};
%(name)s.prototype.is_%(member_name)s = function() {
return this.$tag === %(index)s;
};
''' % {
'name': name,
'member_name': m,
'index': i,
})
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:
underlying_type = _GetUnderlyingPrimitiveType(member.type)
value = (
'(%(member_name)s !== undefined) ? %(member_name)s : ' +
self._CompileConstant(member.maybe_default_value, underlying_type))
elif self.fidl.declarations.get(member.type.identifier) == \
fidl.DeclarationsMap.UNION:
union_compound = _ParseCompoundIdentifier(member.type.identifier)
union_name = _CompileCompoundIdentifier(union_compound)
value = ('(%(member_name)s !== undefined) ? %(member_name)s : ' + 'new '
+ union_name + '()')
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):
"""Ensures there's a type table for the given type, and returns the stem of
its name."""
if t.kind == fidl.TypeKind.PRIMITIVE:
return t.subtype
elif t.kind == fidl.TypeKind.STRING:
return 'String' + ('_Nullable' if t.nullable else '')
elif t.kind == fidl.TypeKind.IDENTIFIER:
compound = _ParseCompoundIdentifier(t.identifier)
name = _CompileCompoundIdentifier(compound)
return name + ('_Nullable' if t.nullable else '')
elif t.kind == fidl.TypeKind.HANDLE or t.kind == fidl.TypeKind.REQUEST:
return 'Handle'
elif t.kind == fidl.TypeKind.ARRAY:
element_ttname = self._CompileType(t.element_type)
ttname = 'ARR_%d_%s' % (t.element_count, element_ttname)
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) {
for (var i = 0; i < %(element_count)s; i++) {
_kTT_%(element_ttname)s.enc(e, o + (i * %(element_size)s), v[i]);
}
},
dec: function(d, o) {
var result = [];
for (var i = 0; i < %(element_count)s; i++) {
result.push(_kTT_%(element_ttname)s.dec(d, o + (i * %(element_size)s)));
}
return result;
},
};
''' % {
'ttname': ttname,
'element_ttname': element_ttname,
'element_count': t.element_count,
'element_size': self._InlineSizeOfType(t.element_type),
})
return ttname
elif t.kind == fidl.TypeKind.VECTOR:
element_ttname = self._CompileType(t.element_type)
ttname = ('VEC_' + ('Nullable_' if t.nullable else '') + element_ttname)
if t.nullable:
handle_null_enc = '''e.data.setUint32(o, 0, $fidl__kLE);
e.data.setUint32(o + 4, 0, $fidl__kLE);
e.data.setUint32(o + 8, 0, $fidl__kLE);
e.data.setUint32(o + 12, 0, $fidl__kLE);
return;
'''
handle_null_dec = 'return null;'
else:
handle_null_enc = 'throw "non-null vector required";'
handle_null_dec = 'throw "non-null vector required";'
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) {
if (v === null || v === undefined) {
%(handle_null_enc)s
}
e.data.setUint32(o, v.length, $fidl__kLE);
e.data.setUint32(o + 4, 0, $fidl__kLE);
e.data.setUint32(o + 8, 0xffffffff, $fidl__kLE);
e.data.setUint32(o + 12, 0xffffffff, $fidl__kLE);
var start = e.alloc(v.length * %(element_size)s);
for (var i = 0; i < v.length; i++) {
_kTT_%(element_ttname)s.enc(e, start + (i * %(element_size)s), v[i]);
}
},
dec: function(d, o) {
var len = d.data.getUint32(o, $fidl__kLE);
var pointer = d.data.getUint32(o + 8, $fidl__kLE);
if (pointer === 0) {
%(handle_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': self._InlineSizeOfType(t.element_type),
'handle_null_enc': handle_null_enc,
'handle_null_dec': handle_null_dec,
})
return ttname
else:
raise NotImplementedError(t.kind)
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;
};
%(proxy_name)s.prototype.$is_bound = function() {
return this.channel != $ZX_HANDLE_INVALID;
};
%(proxy_name)s.prototype.$request = function() {
if (this.$is_bound())
throw "Proxy already bound";
var pair = $ZxChannelCreate();
if (pair.status != $ZX_OK)
throw "ChannelPair creation failed";
this.channel = pair.first;
return pair.second;
};
%(proxy_name)s.prototype.$close = function() {
if (!this.$is_bound())
return;
var status = $zx_handle_close(this.channel);
if (status !== $ZX_OK) {
throw "close handle failed";
}
this.channel = $ZX_HANDLE_INVALID;
};
''' % {
'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()