Source code for textworld.generator.data

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.


from collections import OrderedDict
import os
import glob
from os.path import join as pjoin
from shutil import copyfile, copytree, rmtree
from typing import Optional, Mapping

from textworld.logic import GameLogic
from textworld.generator.vtypes import VariableType, VariableTypeTree
from textworld.utils import maybe_mkdir, RegexDict

BUILTIN_DATA_PATH = os.path.dirname(__file__)
LOGIC_DATA_PATH = pjoin(BUILTIN_DATA_PATH, 'logic')
TEXT_GRAMMARS_PATH = pjoin(BUILTIN_DATA_PATH, 'text_grammars')


def _maybe_copyfile(src, dest, force=False, verbose=False):
    if not os.path.isfile(dest) or force:
        copyfile(src=src, dst=dest)
    else:
        if verbose:
            print("Skipping {} (already exists).".format(dest))


def _maybe_copytree(src, dest, force=False, verbose=False):
    if os.path.exists(dest):
        if force:
            rmtree(dest)
        else:
            if verbose:
                print("Skipping {} (already exists).".format(dest))
            return

    copytree(src=src, dst=dest)


[docs]def create_data_files(dest: str = './textworld_data', verbose: bool = False, force: bool = False): """ Creates grammar files in the target directory. Will NOT overwrite files if they alredy exist (checked on per-file basis). Parameters ---------- dest : The path to the directory where to dump the data files into. verbose : Print when skipping an existing file. force : Overwrite all existing files. """ # Make sure the destination folder exists. maybe_mkdir(dest) # Knowledge base related files. _maybe_copytree(LOGIC_DATA_PATH, pjoin(dest, "logic"), force=force, verbose=verbose) # Text generation related files. _maybe_copytree(TEXT_GRAMMARS_PATH, pjoin(dest, "text_grammars"), force=force, verbose=verbose)
def _to_type_tree(types): vtypes = [] for vtype in sorted(types): if vtype.parents: parent = vtype.parents[0] else: parent = None vtypes.append(VariableType(vtype.name, vtype.name, parent)) return VariableTypeTree(vtypes) def _to_regex_dict(rules): # Sort rules for reproducibility # TODO: Only sort where needed rules = sorted(rules, key=lambda rule: rule.name) rules_dict = OrderedDict() for rule in rules: rules_dict[rule.name] = rule return RegexDict(rules_dict)
[docs]class KnowledgeBase: def __init__(self, logic: GameLogic, text_grammars_path: str): self.logic = logic self.logic_path = "embedded in game" self.text_grammars_path = text_grammars_path self.types = _to_type_tree(self.logic.types) self.rules = _to_regex_dict(self.logic.rules.values()) self.constraints = _to_regex_dict(self.logic.constraints.values()) self.inform7_commands = {i7cmd.rule: i7cmd.command for i7cmd in self.logic.inform7.commands.values()} self.inform7_events = {i7cmd.rule: i7cmd.event for i7cmd in self.logic.inform7.commands.values()} self.inform7_predicates = {i7pred.predicate.signature: (i7pred.predicate, i7pred.source) for i7pred in self.logic.inform7.predicates.values()} self.inform7_variables = {i7type.name: i7type.kind for i7type in self.logic.inform7.types.values()} self.inform7_variables_description = {i7type.name: i7type.definition for i7type in self.logic.inform7.types.values()} self.inform7_addons_code = self.logic.inform7.code
[docs] @classmethod def default(cls): return KB
[docs] @classmethod def load(cls, target_dir: Optional[str] = None, logic_path: Optional[str] = None, grammar_path: Optional[str] = None) -> "KnowledgeBase": """ Build a KnowledgeBase from several files (logic and text grammar). Args: target_dir: Folder containing two subfolders: `logic` and `text_grammars`. If provided, both `logic_path` and `grammar_path` are ignored. logic_path: Folder containing `*.twl` files that describe the logic of a game. grammar_path: Folder containing `*.twg` files that describe the grammar used for text generation. Returns: KnowledgeBase object. """ if target_dir: logic_path = pjoin(target_dir, "logic") grammar_path = pjoin(target_dir, "text_grammars") if logic_path is None: logic_path = pjoin(".", "textworld_data", "logic") # Check within working dir. if not os.path.isdir(logic_path): logic_path = LOGIC_DATA_PATH # Default to built-in data. # Load knowledge base related files. paths = glob.glob(pjoin(logic_path, "*.twl")) logic = GameLogic.load(paths) if grammar_path is None: grammar_path = pjoin(".", "textworld_data", "text_grammars") # Check within working dir. if not os.path.isdir(grammar_path): grammar_path = TEXT_GRAMMARS_PATH # Default to built-in data. # Load text generation related files. kb = cls(logic, grammar_path) kb.logic_path = logic_path return kb
[docs] def get_reverse_action(self, action): r_name = self.logic.reverse_rules.get(action.name) if r_name: return action.inverse(name=r_name) else: return None
[docs] @classmethod def deserialize(cls, data: Mapping) -> "KnowledgeBase": logic = GameLogic.deserialize(data["logic"]) text_grammars_path = data["text_grammars_path"] return cls(logic, text_grammars_path)
[docs] def serialize(self) -> str: data = { "logic": self.logic.serialize(), "text_grammars_path": self.text_grammars_path, } return data
def __str__(self) -> str: infos = [] infos.append("logic_path: {}".format(self.logic_path)) infos.append("grammar_path: {}".format(self.text_grammars_path)) infos.append("nb_rules: {}".format(len(self.logic.rules))) infos.append("nb_types: {}".format(len(self.logic.types))) return "\n".join(infos)
# On module load. KB = KnowledgeBase.load()