| # Copyright 2015 Google Inc. All Rights Reserved. |
| # coding=utf-8 |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Functions for generating bindings for overloaded operations/constructors.""" |
| |
| from functools import partial |
| |
| from v8_interface import distinguishing_argument_index |
| from v8_interface import effective_overload_set_by_length |
| |
| |
| def get_overload_contexts(expression_generator, method_overloads): |
| """Create overload contexts for overloaded methods/constructors. |
| |
| Arguments: |
| expression_generator: An ExpressionGenerator instance. |
| method_overloads: [overload_sets] where overload_sets is itself |
| a list of method_contexts (or constructor_contexts). All methods |
| in the list of method_contexts are overloads of the same symbol. |
| Returns: |
| [overload_contexts] |
| """ |
| overload_contexts = [] |
| for overloaded_methods in method_overloads: |
| # Create an overload context. Binding generation will use this as a top |
| # level context, and the identifier will be bound to an overload |
| # resolution function. |
| overload_context = { |
| 'overloads': overloaded_methods, |
| } |
| |
| # Add a property to the overloaded methods indicating their overload |
| # indices. This allows us to refer to each overload with a unique name. |
| for index, method in enumerate(overloaded_methods, 1): |
| method['overload_index'] = index |
| |
| # Get the effective overload set, and group by the number of arguments. |
| # This is a list of tuples (numargs, [overload_set]) |
| effective_overloads_by_length = ( |
| effective_overload_set_by_length(overloaded_methods)) |
| |
| overload_context['length'] = min( |
| [length for length, _ in effective_overloads_by_length]) |
| |
| # The first four steps of the overload resolution algorithm involve |
| # removing entries from the set S of all overloads where the number |
| # of arguments in the overload does not match the number of overloads |
| # passed to the function. |
| # We accomplish this by partitioning the set S into S0...S{maxarg} where |
| # each S{n} contains overloads with n arguments, and then switch on |
| # the number of arguments in the bindings code. |
| # |
| # http://heycam.github.io/webidl/#dfn-overload-resolution-algorithm |
| # 1. Let maxarg be the length of the longest type list of the entries |
| # in S. |
| # 2. Initialize argcount to be min(maxarg, n). |
| # 3. Remove from S all entries whose type list is not of length |
| # argcount. |
| # |
| # overload_resolution_context_by_length will be a list of tuples |
| # (length, distinguishing_index, [overload_resolution_tests]) |
| overload_resolution_context_by_length = [] |
| for length, overload_set in effective_overloads_by_length: |
| # For each overload, create a list of tuples for resolution: |
| # (length, distinguishing_argument_index, [resolution_tests]) |
| # where the length refers to the number of arguments for the set Sn of |
| # overloads that take that number of arguments, |
| # distinguishing_argument_index is as defined in the spec (except we |
| # use None in the case that the size of the set Sn is 1), and |
| # resolution tests is a list of tuples: |
| # (test_callable, method_context) |
| # where test_callable is a function that takes one argument which is a |
| # string representing the name of a variable in the generated bindings |
| # code, and returns a boolean expression. If this evaluates to true, |
| # then the test's associated overloaded method_context should be called. |
| # |
| # http://heycam.github.io/webidl/#dfn-overload-resolution-algorithm |
| # 7. If there is more than one entry in S, then set d to be the |
| # distinguishing argument index for the entries of S. |
| overload_resolution_context_by_length.append( |
| (length, distinguishing_argument_index(overload_set) |
| if len(overload_set) > 1 else None, |
| list(resolution_tests_methods(expression_generator, overload_set)))) |
| overload_context['overload_resolution_by_length'] = ( |
| overload_resolution_context_by_length) |
| |
| overload_contexts.append(overload_context) |
| |
| return overload_contexts |
| |
| |
| def resolution_tests_methods(expression_generator, effective_overloads): |
| """Yields resolution test and associated method. |
| |
| Tests and methods are in resolution order for effective overloads of a given |
| length. |
| |
| This is the heart of the resolution algorithm. |
| http://heycam.github.io/webidl/#dfn-overload-resolution-algorithm |
| |
| This function is based off of v8_interface.resolution_tests_methods. In Blink |
| the first item in the tuple is a string representing C++ code that can be |
| executed to perform the test. In Cobalt it is a function that takes one |
| argument which is a string representing the variable name of a C++ object |
| on which the test should be performed, and returns an expression that |
| performs the test. |
| |
| Args: |
| expression_generator: An ExpressionGenerator instance. |
| effective_overloads: List of tuples returned from |
| effective_overload_set_by_length |
| Yields: |
| (test, method) |
| """ |
| |
| methods = [ |
| effective_overload[0] for effective_overload in effective_overloads |
| ] |
| if len(methods) == 1: |
| # If only one method with a given length, no test needed |
| yield lambda arg: 'true', methods[0] |
| return |
| |
| # 6. If there is more than one entry in S, then set d to be the |
| # distinguishing argument index for the entries of S. |
| index = distinguishing_argument_index(effective_overloads) |
| # (7-9 are for handling |undefined| values for optional arguments before |
| # the distinguishing argument (as "missing"), so you can specify only some |
| # optional arguments. We don't support this, so we skip these steps.) |
| # 10. If i = d, then: |
| # (d is the distinguishing argument index) |
| # 1. Let V be argi. |
| # Note: This is the argument that will be used to resolve which |
| # overload is selected. |
| # js_value = 'info[%s]' % index |
| |
| # Extract argument and IDL type to simplify accessing these in each loop. |
| arguments = [method['arguments'][index] for method in methods] |
| arguments_methods = zip(arguments, methods) |
| idl_types = [argument['idl_type_object'] for argument in arguments] |
| idl_types_methods = zip(idl_types, methods) |
| |
| # We can't do a single loop through all methods or simply sort them, because |
| # a method may be listed in multiple steps of the resolution algorithm, and |
| # which test to apply differs depending on the step. |
| # |
| # Instead, we need to go through all methods at each step, either finding |
| # first match (if only one test is allowed) or filtering to matches (if |
| # multiple tests are allowed), and generating an appropriate tests. |
| |
| # 2. If V is undefined, and there is an entry in S whose list of |
| # optionality values has "optional" at index i, then remove from S all |
| # other entries. |
| try: |
| method = next(method for argument, method in arguments_methods |
| if argument['is_optional']) |
| yield expression_generator.is_undefined, method |
| except StopIteration: |
| pass |
| |
| # 3. Otherwise: if V is null or undefined, and there is an entry in S that |
| # has one of the following types at position i of its type list, |
| # - a nullable type |
| try: |
| method = next(method for idl_type, method in idl_types_methods |
| if idl_type.is_nullable) |
| yield expression_generator.is_undefined_or_null, method |
| except StopIteration: |
| pass |
| |
| # 4. Otherwise: if V is a platform object - but not a platform array |
| # object - and there is an entry in S that has one of the following |
| # types at position i of its type list, |
| # - an interface type that V implements |
| # (Unlike most of these tests, this can return multiple methods, since we |
| # test if it implements an interface. Thus we need a for loop, not a next.) |
| # (We distinguish wrapper types from built-in interface types.) |
| for idl_type, method in ((idl_type, method) |
| for idl_type, method in idl_types_methods |
| if idl_type.is_wrapper_type): |
| base_type = idl_type.base_type if idl_type.is_nullable else idl_type |
| yield partial(expression_generator.inherits_interface, base_type), method |
| |
| # 8. Otherwise: if V is any kind of object except for a native Date object, |
| # a native RegExp object, and there is an entry in S that has one of the |
| # following types at position i of its type list, |
| # - an array type |
| # - a sequence type |
| # ... |
| # - a dictionary |
| # |
| # FIXME: |
| # We don't strictly follow the algorithm here. The algorithm says "remove |
| # all other entries" if there is "one entry" matching, but we yield all |
| # entries to support following constructors: |
| # [constructor(sequence<DOMString> arg), constructor(Dictionary arg)] |
| # interface I { ... } |
| # (Need to check array types before objects because an array is an object) |
| for idl_type, method in idl_types_methods: |
| if idl_type.native_array_element_type: |
| raise NotImplementedError('Array types not yet supported') |
| for idl_type, method in idl_types_methods: |
| if idl_type.is_dictionary or idl_type.name == 'Dictionary': |
| raise NotImplementedError('Dictionary types not yet supported.') |
| |
| # (Check for exact type matches before performing automatic type conversion; |
| # only needed if distinguishing between primitive types.) |
| if len([idl_type.is_primitive_type for idl_type in idl_types]) > 1: |
| # (Only needed if match in step 11, otherwise redundant.) |
| if any(idl_type.is_string_type or idl_type.is_enum |
| for idl_type in idl_types): |
| # 10. Otherwise: if V is a Number value, and there is an entry in S |
| # that has one of the following types at position i of its type |
| # list, |
| # - a numeric type |
| try: |
| method = next(method for idl_type, method in idl_types_methods |
| if idl_type.is_numeric_type) |
| yield (expression_generator.is_number), method |
| except StopIteration: |
| pass |
| |
| # (Perform automatic type conversion, in order. If any of these match, |
| # that's the end, and no other tests are needed.) To keep this code simple, |
| # we rely on the C++ compiler's dead code elimination to deal with the |
| # redundancy if both cases below trigger. |
| |
| # 11. Otherwise: if there is an entry in S that has one of the following |
| # types at position i of its type list, |
| # - DOMString |
| # - ByteString |
| # - USVString |
| # - an enumeration type |
| try: |
| method = next(method for idl_type, method in idl_types_methods |
| if idl_type.is_string_type or idl_type.is_enum) |
| yield lambda arg: 'true', method |
| except StopIteration: |
| pass |
| |
| # 12. Otherwise: if there is an entry in S that has one of the following |
| # types at position i of its type list, |
| # - a numeric type |
| try: |
| method = next(method for idl_type, method in idl_types_methods |
| if idl_type.is_numeric_type) |
| yield lambda arg: 'true', method |
| except StopIteration: |
| pass |