def test_lisp_to_nested_expression(self): logical_form = "((reverse fb:row.row.year) (fb:row.row.league fb:cell.usl_a_league))" expression = util.lisp_to_nested_expression(logical_form) assert expression == [['reverse', 'fb:row.row.year'], ['fb:row.row.league', 'fb:cell.usl_a_league']] logical_form = "(count (and (division 1) (tier (!= null))))" expression = util.lisp_to_nested_expression(logical_form) assert expression == ['count', ['and', ['division', '1'], ['tier', ['!=', 'null']]]]
def test_lisp_to_nested_expression(self): logical_form = "((reverse fb:row.row.year) (fb:row.row.league fb:cell.usl_a_league))" expression = util.lisp_to_nested_expression(logical_form) assert expression == [ ["reverse", "fb:row.row.year"], ["fb:row.row.league", "fb:cell.usl_a_league"], ] logical_form = "(count (and (division 1) (tier (!= null))))" expression = util.lisp_to_nested_expression(logical_form) assert expression == [ "count", ["and", ["division", "1"], ["tier", ["!=", "null"]]] ]
def parse_logical_form(self, logical_form: str, remove_var_function: bool = True) -> Expression: """ Takes a logical form as a string, maps its tokens using the mapping and returns a parsed expression. Parameters ---------- logical_form : ``str`` Logical form to parse remove_var_function : ``bool`` (optional) ``var`` is a special function that some languages use within lambda functions to indicate the usage of a variable. If your language uses it, and you do not want to include it in the parsed expression, set this flag. You may want to do this if you are generating an action sequence from this parsed expression, because it is easier to let the decoder not produce this function due to the way constrained decoding is currently implemented. """ if not logical_form.startswith("("): logical_form = f"({logical_form})" if remove_var_function: # Replace "(x)" with "x" logical_form = re.sub(r'\(([x-z])\)', r'\1', logical_form) # Replace "(var x)" with "(x)" logical_form = re.sub(r'\(var ([x-z])\)', r'(\1)', logical_form) parsed_lisp = util.lisp_to_nested_expression(logical_form) translated_string = self._process_nested_expression(parsed_lisp) type_signature = self.local_type_signatures.copy() type_signature.update(self.global_type_signatures) return self._logic_parser.parse(translated_string, signature=type_signature)
def logical_form_to_action_sequence(self, logical_form: str) -> List[str]: """ Converts a logical form into a linearization of the production rules from its abstract syntax tree. The linearization is top-down, depth-first. Each production rule is formatted as "LHS -> RHS", where "LHS" is a single non-terminal type, and RHS is either a terminal or a list of non-terminals (other possible values for RHS in a more general context-free grammar are not produced by our grammar induction logic). Non-terminals are `types` in the grammar, either basic types (like ``int``, ``str``, or some class that you define), or functional types, represented with angle brackets with a colon separating arguments from the return type. Multi-argument functions have commas separating their argument types. For example, ``<int:int>`` is a function that takes an integer and returns an integer, and ``<int,int:int>`` is a function that takes two integer arguments and returns an integer. As an example translation from logical form to complete action sequence, the logical form ``(add 2 3)`` would be translated to ``['@start@ -> int', 'int -> [<int,int:int>, int, int]', '<int,int:int> -> add', 'int -> 2', 'int -> 3']``. """ expression = util.lisp_to_nested_expression(logical_form) try: transitions, start_type = self._get_transitions(expression, expected_type=None) if self._start_types and start_type not in self._start_types: raise ParsingError( f"Expression had unallowed start type of {start_type}: {expression}" ) except ParsingError as error: logger.error( f'Error parsing logical form: {logical_form}: {error}') raise transitions.insert(0, f'@start@ -> {start_type}') return transitions
def execute(self, logical_form: str): """Executes a logical form, using whatever predicates you have defined.""" if not hasattr(self, "_functions"): raise RuntimeError("You must call super().__init__() in your Language constructor") logical_form = logical_form.replace(",", " ") expression = util.lisp_to_nested_expression(logical_form) return self._execute_expression(expression)
def execute(self, lf_raw: str) -> int: """ Very basic model for executing friction logical forms. For now returns answer index (or -1 if no answer can be concluded) """ # Remove "a:" prefixes from attributes (hack) logical_form = re.sub(r"\(a:", r"(", lf_raw) parse = util.lisp_to_nested_expression(logical_form) if len(parse) < 2: return -1 if parse[0] == 'infer': args = [self._exec_and(arg) for arg in parse[1:]] if None in args: return -1 return self._exec_infer(*args) return -1
def main(): def nested_expression_to_lisp(nested_expression): if isinstance(nested_expression, str): return nested_expression elif isinstance(nested_expression, List): lisp_expressions = [nested_expression_to_lisp(x) for x in nested_expression] return "(" + " ".join(lisp_expressions) + ")" else: raise NotImplementedError drop_language = DROPLanguage() DROP_predicates = sorted(list(drop_language._functions.keys())) print(DROP_predicates) print("Non termincal prods") non_terminal_prods = drop_language.get_nonterminal_productions() print("\n".join(non_terminal_prods)) print("\n") print("All possible prods") all_possible_prods = drop_language.all_possible_productions() print("\n".join(all_possible_prods)) exit() # program = "(COMPARATIVE (SELECT GET_QUESTION_SPAN) (PARTIAL_GROUP_count (PARTIAL_PROJECT GET_QUESTION_SPAN)) (CONDITION GET_QUESTION_SPAN))" program = "(COMPARATIVE (SELECT GET_QUESTION_SPAN) (PSSA GET_QUESTION_SPAN) (CONDITION GET_QUESTION_SPAN))" nested_expression = lisp_to_nested_expression(program) action_seq = drop_language.logical_form_to_action_sequence(program) print(nested_expression) print(action_seq) nested_expression = ['COMPARATIVE', ['SELECT', 'nationalities registered in Bilbao'], ['PARTIAL_GROUP_count', ['PARTIAL_PROJECT', 'people of #REF']], ['CONDITION', 'is higher than 10'] ]
def get_explanation(logical_form: str, world_extractions: JsonDict, answer_index: int, world: QuarelWorld) -> List[JsonDict]: """ Create explanation (as a list of header/content entries) for an answer """ output = [] nl_world = {} if world_extractions["world1"] != "N/A" and world_extractions[ "world1"] != ["N/A"]: nl_world["world1"] = nl_world_string(world_extractions["world1"]) nl_world["world2"] = nl_world_string(world_extractions["world2"]) output.append({ "header": "Identified two worlds", "content": [ f"""world1 = {nl_world['world1']}""", f"""world2 = {nl_world['world2']}""", ], }) else: nl_world["world1"] = "world1" nl_world["world2"] = "world2" parse = util.lisp_to_nested_expression(logical_form) if parse[0] != "infer": return None setup = parse[1] output.append({ "header": "The question is stating", "content": nl_arg(setup, nl_world) }) answers = parse[2:] output.append({ "header": "The answer options are stating", "content": [ "A: " + " and ".join(nl_arg(answers[0], nl_world)), "B: " + " and ".join(nl_arg(answers[1], nl_world)), ], }) setup_core = setup if setup[0] == "and": setup_core = setup[1] s_attr = setup_core[0] s_dir = world.qr_size[setup_core[1]] s_world = nl_world[setup_core[2]] a_attr = answers[answer_index][0] qr_dir = world._get_qr_coeff(strip_entity_type(s_attr), strip_entity_type(a_attr)) a_dir = s_dir * qr_dir a_world = nl_world[answers[answer_index][2]] content = [ f"When {nl_attr(s_attr)} is {nl_dir(s_dir)} " + f"then {nl_attr(a_attr)} is {nl_dir(a_dir)} (for {s_world})" ] if a_world != s_world: content.append( f"""Therefore {nl_attr(a_attr)} is {nl_dir(-a_dir)} for {a_world}""" ) content.append(f"Therefore {chr(65+answer_index)} is the correct answer") output.append({"header": "Theory used", "content": content}) return output