コード例 #1
0
 def _execute_expression(self, expression: Any):
     """
     This does the bulk of the work of executing a logical form, recursively executing a single
     expression.  Basically, if the expression is a function we know about, we evaluate its
     arguments then call the function.  If it's a list, we evaluate all elements of the list.
     If it's a constant (or a zero-argument function), we evaluate the constant.
     """
     # pylint: disable=too-many-return-statements
     if isinstance(expression, list):
         if isinstance(expression[0], list):
             function = self._execute_expression(expression[0])
         elif expression[0] in self._functions:
             function = self._functions[expression[0]]
         else:
             if isinstance(expression[0], str):
                 raise ExecutionError(
                     f"Unrecognized function: {expression[0]}")
             else:
                 raise ExecutionError(
                     f"Unsupported expression type: {expression}")
         arguments = [
             self._execute_expression(arg) for arg in expression[1:]
         ]
         try:
             return function(*arguments)
         except (TypeError, ValueError):
             traceback.print_exc()
             raise ExecutionError(
                 f"Error executing expression {expression} (see stderr for stack trace)"
             )
     elif isinstance(expression, str):
         if expression not in self._functions:
             raise ExecutionError(f"Unrecognized constant: {expression}")
         # This is a bit of a quirk in how we represent constants and zero-argument functions.
         # For consistency, constants are wrapped in a zero-argument lambda.  So both constants
         # and zero-argument functions are callable in `self._functions`, and are `BasicTypes`
         # in `self._function_types`.  For these, we want to return
         # `self._functions[expression]()` _calling_ the zero-argument function.  If we get a
         # `FunctionType` in here, that means we're referring to the function as a first-class
         # object, instead of calling it (maybe as an argument to a higher-order function).  In
         # that case, we return the function _without_ calling it.
         # Also, we just check the first function type here, because we assume you haven't
         # registered the same function with both a constant type and a `FunctionType`.
         if isinstance(self._function_types[expression][0], FunctionType):
             return self._functions[expression]
         else:
             return self._functions[expression]()
         return self._functions[expression]
     else:
         raise ExecutionError(
             "Not sure how you got here. Please open a github issue with details."
         )
コード例 #2
0
def execute_action_sequence(language,
                            action_sequence: List[str],
                            side_arguments: List[Dict] = None):
    """
    Executes the program defined by an action sequence directly, without needing the overhead
    of translating to a logical form first.  For any given program, :func:`execute` and this
    function are equivalent, they just take different representations of the program, so you
    can use whichever is more efficient.

    Also, if you have state or side arguments associated with particular production rules
    (e.g., the decoder's attention on an input utterance when a predicate was predicted), you
    `must` use this function to execute the logical form, instead of :func:`execute`, so that
    we can match the side arguments with the right functions.
    """

    # We'll strip off the first action, because it doesn't matter for execution.
    first_action = action_sequence[0]
    left_side, right_side = first_action.split(" -> ")
    if left_side != "@start@":
        raise ExecutionError("invalid action sequence")
    remaining_side_args = side_arguments[1:] if side_arguments else None

    execution_vals = []

    execution_value, _, _, execution_vals = _execute_sequence(
        language, action_sequence[1:], remaining_side_args, execution_vals)

    return execution_value, execution_vals[0]
コード例 #3
0
 def min_number(self, rows: List[Row], column: NumberColumn) -> Number:
     """
     Takes a list of rows and a column and returns the min of the values under that column in
     those rows.
     """
     cell_values = [row.values[column.name] for row in rows if row.values[column.name] is not None]
     if not cell_values:
         return 0.0  # type: ignore
     if not all([isinstance(value, Number) for value in cell_values]):
         raise ExecutionError(f"Invalid values for number selection function: {cell_values}")
     return min(cell_values)  # type: ignore
コード例 #4
0
 def min_date(self, rows: List[Row], column: DateColumn) -> Date:
     """
     Takes a list of rows and a column and returns the min of the values under that column in
     those rows.
     """
     cell_values = [row.values[column.name] for row in rows if row.values[column.name] is not None]
     if not cell_values:
         return Date(-1, -1, -1)
     if not all([isinstance(value, Date) for value in cell_values]):
         raise ExecutionError(f"Invalid values for date selection function: {cell_values}")
     return min(cell_values)  # type: ignore
コード例 #5
0
 def mode_string(self, rows: List[Row], column: StringColumn) -> List[str]:
     """
     Takes a list of rows and a column and returns the most frequent values (one or more) under
     that column in those rows.
     """
     most_frequent_list = self._get_most_frequent_values(rows, column)
     if not most_frequent_list:
         return []
     if not all([isinstance(value, str) for value in most_frequent_list]):
         raise ExecutionError(f"Invalid values for mode_string: {most_frequent_list}")
     return most_frequent_list
コード例 #6
0
 def mode_date(self, rows: List[Row], column: DateColumn) -> Date:
     """
     Takes a list of rows and a column and returns the most frequent value under
     that column in those rows.
     """
     most_frequent_list = self._get_most_frequent_values(rows, column)
     if not most_frequent_list:
         return Date(-1, -1, -1)
     most_frequent_value = most_frequent_list[0]
     if not isinstance(most_frequent_value, Date):
         raise ExecutionError(f"Invalid values for mode_date: {most_frequent_value}")
     return most_frequent_value
コード例 #7
0
 def mode_number(self, rows: List[Row], column: NumberColumn) -> Number:
     """
     Takes a list of rows and a column and returns the most frequent value under
     that column in those rows.
     """
     most_frequent_list = self._get_most_frequent_values(rows, column)
     if not most_frequent_list:
         return 0.0  # type: ignore
     most_frequent_value = most_frequent_list[0]
     if not isinstance(most_frequent_value, Number):
         raise ExecutionError(f"Invalid values for mode_number: {most_frequent_value}")
     return most_frequent_value
コード例 #8
0
 def filter_not_in(self, rows: List[Row], column: StringColumn, filter_values: List[str]) -> List[Row]:
     # We accept a list of filter values instead of a single string to allow the outputs of select like
     # operations to be passed in as filter values.
     # Assuming filter value has underscores for spaces. The cell values also have underscores
     # for spaces, so we do not need to replace them here.
     # Note that if a list of filter values is passed, we only use the first one.
     if not filter_values:
         raise ExecutionError(f"Unexpected filter value: {filter_values}")
     if isinstance(filter_values, str):
         filter_value = filter_values
     elif isinstance(filter_values, list):
         filter_value = filter_values[0]
     else:
         raise ExecutionError(f"Unexpected filter value: {filter_values}")
     # Also, we need to remove the "string:" that was prepended to the entity name in the language.
     filter_value = filter_value.lstrip('string:')
     filtered_rows: List[Row] = []
     for row in rows:
         cell_value = row.values[column.name]
         if isinstance(cell_value, str) and filter_value not in cell_value:
             filtered_rows.append(row)
     return filtered_rows
コード例 #9
0
 def diff(self, first_row: List[Row], second_row: List[Row], column: NumberColumn) -> Number:
     """
     Takes a two rows and a number column and returns the difference between the values under
     that column in those two rows.
     """
     if not first_row or not second_row:
         return 0.0  # type: ignore
     first_value = first_row[0].values[column.name]
     second_value = second_row[0].values[column.name]
     if isinstance(first_value, float) and isinstance(second_value, float):
         return first_value - second_value  # type: ignore
     elif first_value is None or second_value is None:
         return 0.0  # type: ignore
     else:
         raise ExecutionError(f"Invalid column for diff: {column.name}")
コード例 #10
0
    def _execute_sequence(
            self, action_sequence: List[str],
            side_arguments: List[Dict]) -> Tuple[Any, List[str], List[Dict]]:
        """
        This does the bulk of the work of :func:`execute_action_sequence`, recursively executing
        the functions it finds and trimming actions off of the action sequence.  The return value
        is a tuple of (execution, remaining_actions), where the second value is necessary to handle
        the recursion.
        """
        if not action_sequence:
            raise ExecutionError("invalid action sequence")
        first_action = action_sequence[0]
        remaining_actions = action_sequence[1:]
        remaining_side_args = side_arguments[1:] if side_arguments else None
        right_side = first_action.split(' -> ')[1]
        if right_side in self._functions:
            function = self._functions[right_side]
            # mypy doesn't like this check, saying that Callable isn't a reasonable thing to pass
            # here.  But it works just fine; I'm not sure why mypy complains about it.
            if isinstance(function, Callable):  # type: ignore
                function_arguments = inspect.signature(function).parameters
                if not function_arguments:
                    # This was a zero-argument function / constant that was registered as a lambda
                    # function, for consistency of execution in `execute()`.
                    execution_value = function()
                elif side_arguments:
                    kwargs = {}
                    non_kwargs = []
                    for argument_name in function_arguments:
                        if argument_name in side_arguments[0]:
                            kwargs[argument_name] = side_arguments[0][
                                argument_name]
                        else:
                            non_kwargs.append(argument_name)
                    if kwargs and non_kwargs:
                        # This is a function that has both side arguments and logical form
                        # arguments - we curry the function so only the logical form arguments are
                        # left.
                        def curried_function(*args):
                            return function(*args, **kwargs)

                        execution_value = curried_function
                    elif kwargs:
                        # This is a function that _only_ has side arguments - we just call the
                        # function and return a value.
                        execution_value = function(**kwargs)
                    else:
                        # This is a function that has logical form arguments, but no side arguments
                        # that match what we were given - just return the function itself.
                        execution_value = function
                else:
                    execution_value = function
            return execution_value, remaining_actions, remaining_side_args
        else:
            # This is a non-terminal expansion, like 'int -> [<int:int>, int, int]'.  We need to
            # get the function and its arguments, then call the function with its arguments.
            # Because we linearize the abstract syntax tree depth first, left-to-right, we can just
            # recursively call `_execute_sequence` for the function and all of its arguments, and
            # things will just work.
            right_side_parts = right_side.split(', ')

            # We don't really need to know what the types are, just how many of them there are, so
            # we recurse the right number of times.
            function, remaining_actions, remaining_side_args = self._execute_sequence(
                remaining_actions, remaining_side_args)
            arguments = []
            for _ in right_side_parts[1:]:
                argument, remaining_actions, remaining_side_args = self._execute_sequence(
                    remaining_actions, remaining_side_args)
                arguments.append(argument)
            return function(*arguments), remaining_actions, remaining_side_args