Exemplo n.º 1
0
def main():
    # Create a grammar and add some rules to it
    grammar = Grammar()
    name = HiddenRule("name", AlternativeSet("john", "bob", "anna"))

    # greeting is either: 'hey', 'hey there' or 'hello'
    greeting = HiddenRule(
        "greeting",
        AlternativeSet(Sequence("hey", OptionalGrouping("there")), "hello"))

    # parting_phrase is either: 'good bye' or 'see you'
    parting_phrase = HiddenRule("parting_phrase",
                                AlternativeSet("good bye", "see you"))

    # greet is a greeting followed by a name
    greet = PublicRule("greet", Sequence(RuleRef(greeting), RuleRef(name)))

    # goodbye is a parting phrase followed by a name
    goodbye = PublicRule("goodbye",
                         Sequence(RuleRef(parting_phrase), RuleRef(name)))

    grammar.add_rules(name, greeting, parting_phrase, greet, goodbye)

    print("Grammar compiles to the following:")
    print(grammar.compile())

    # Try matching some speech strings
    print_matching(grammar, "hey john")
    print_matching(grammar, "hey there john")
    print_matching(grammar, "see you john")

    # Try matching some hidden rules
    print_matching(grammar, "bob")
    print_matching(grammar, "hey there")
    print_matching(grammar, "good bye")
Exemplo n.º 2
0
def main():
    # Define a open/close file rule.
    open, close = Literal("open"), Literal("close")
    open.tag, close.tag = "OPEN", "CLOSE"
    cmd = PublicRule("command",
                     Sequence(AlternativeSet(open, close), "the file"))

    # Print the tags of the 'command' rule.
    print("Tags: %s\n" % cmd.tags)

    # Initialise a new grammar and add the rule to it.
    g = Grammar()
    g.add_rule(cmd)

    # Print the compiled grammar
    print("Compiled grammar is:\n%s" % g.compile())

    # Find and print rules tagged with "OPEN"
    print("Tagged rules are:\n%s\n" % g.find_tagged_rules("OPEN"))

    # Matching tags can be retrieved using r.get_tags_matching
    # The Rule.matched_tags property can also be used if Rule.matches or
    # Grammar.find_matching_rules has been called first.
    speech = "open the file"
    print("Tags matching '%s' are: %s" %
          (speech, cmd.get_tags_matching(speech)))
Exemplo n.º 3
0
    def register_grammar(self, name: str, grammar: Grammar) -> None:
        if name in self._registered_grammars:
            raise KeyError(f'Grammar with name "{name}" is already registered')
        grammar_file = self._tmp_dir / _make_grammar_filename(name)
        with open(grammar_file, 'w') as f:
            f.write(grammar.compile())

        self.decoder.set_jsgf_file(_make_grammar_name(name), str(grammar_file))
        self._registered_grammars.add(name)
Exemplo n.º 4
0
def main():
    # Create a hidden (private) rule
    rule1 = HiddenRule("hello", "hello")

    # Create a public rule referencing rule1
    rule2 = PublicRule("greet", RuleRef(rule1))

    # Create a grammar and add the new rules to it
    grammar = Grammar("g")
    grammar.add_rules(rule1, rule2)

    # Compile the grammar using compile()
    print("Grammar '%s' compiles to:" % grammar.name)
    print(grammar.compile())

    # Find rules matching 'hello'
    # rule2 will be found, but not rule1 because it is hidden
    print("Matching rule: %s" % grammar.find_matching_rules("hello")[0])
Exemplo n.º 5
0
def main():
    # The Sequence expansion requires all of its children expansions to be spoken
    # in sequence. The OptionalGrouping expansion optionally requires its child
    # expansion to be spoken.

    # Create a public rule using an optional expansion
    rule = PublicRule("greet", Sequence("hey", OptionalGrouping("there")))

    # Create a grammar and add the new rule to it
    grammar = Grammar("g")
    grammar.add_rule(rule)

    # Compile the grammar using compile()
    print("Grammar '%s' compiles to:" % grammar.name)
    print(grammar.compile())

    # Use or do not use the optional word 'there'
    print_matching(grammar, "hey")
    print_matching(grammar, "hey there")
Exemplo n.º 6
0
def main():
    # Create a public rule with the name 'hello' and a Literal expansion
    # 'hello world'.
    rule = PublicRule("hello", Literal("hello world"))

    # Note that the following creates the same rule:
    rule = PublicRule("hello", "hello world")

    # Create a grammar and add the new rule to it
    grammar = Grammar()
    grammar.add_rule(rule)

    # Compile the grammar using compile()
    # compile_to_file(file_path) may be used to write a compiled grammar to
    # a file instead.
    # Compilation is not required for finding matching rules.
    print(grammar.compile())

    # Find rules in the grammar that match 'hello world'.
    matching = grammar.find_matching_rules("hello world")
    print("Matching: %s" % matching[0])
Exemplo n.º 7
0
def main():
    # The Repeat expansion requires one or more matches for its child expansion.
    # The KleeneStar requires zero or more matches. The Sequence expansion
    # requires all of its children expansions to be spoken in sequence.

    # Create a public rule using a Repeat expansion and another using the
    # KleeneStar expansion.
    rule1 = PublicRule("repeat", Sequence(Repeat("please"), "don't crash"))
    rule2 = PublicRule("kleene", Sequence(KleeneStar("please"), "don't crash"))

    # Create a grammar and add the new rules to it
    grammar = Grammar("g")
    grammar.add_rules(rule1, rule2)

    # Compile the grammar using compile()
    print("Grammar '%s' compiles to:" % grammar.name)
    print(grammar.compile())

    # Find rules in the grammar that match some speech strings
    print_matching(grammar, "don't crash")  # only kleene will match
    print_matching(grammar, "please don't crash")  # both will match
    print_matching(grammar, "please please don't crash")  # both again
def main():
    # Create a new public rule using speech alternatives
    # Note that the Sequence expansion requires all of its children expansions
    # to be spoken in sequence
    rule = PublicRule("greet", Sequence(AlternativeSet("hello", "hey"),
                                        "there"))

    # Create a grammar and add the new rule to it
    grammar = Grammar("g")
    grammar.add_rule(rule)

    # Compile the grammar using compile()
    print("Grammar '%s' compiles to:" % grammar.name)
    print(grammar.compile())

    # Find rules in the grammar that match some speech strings
    print_matching(grammar, "hello there")
    print_matching(grammar, "hey there")

    # 'hello hey there' will not match because only one alternative in an
    # AlternativeSet expansion can be matched
    print_matching(grammar, "hello hey there")
Exemplo n.º 9
0
class DictationGrammar(Grammar):
    """
    Grammar subclass that processes rules that use Dictation expansions so they can
    be compiled, matched and used with normal JSGF rules.
    """
    def __init__(self, rules=None, name="default"):
        """
        :type rules: list
        :type name: str
        """
        super(DictationGrammar, self).__init__(name)
        self._dictation_rules = []
        self._original_rule_map = {}
        self._init_jsgf_only_grammar()

        if rules:
            self.add_rules(*rules)

    def _init_jsgf_only_grammar(self):
        """
        Method that initialises the grammar to use for rules not containing
        Dictation expansions.

        Override this to use a different grammar class.
        """
        self._jsgf_only_grammar = Grammar(name=self.name)

    @property
    def rules(self):
        """
        The rules in this grammar.
        This includes internal generated rules as well as original rules.
        :rtype: list
        """
        return list(
            set(self._dictation_rules + self._jsgf_only_grammar.match_rules +
                list(self._original_rule_map.values())))

    @property
    def match_rules(self):
        """
        The rules that the find_matching_rules method will match against.
        :return: iterable
        """
        result = []
        result.extend([x for x in self._dictation_rules if x.visible])
        result.extend(self._jsgf_only_grammar.match_rules)
        return result

    def add_rule(self, rule):
        if not isinstance(rule, Rule):
            raise TypeError("object '%s' was not a JSGF Rule object" % rule)

        # Check if the same rule is already in the grammar.
        if rule.name in self.rule_names:
            if rule in self.rules:
                # Silently return if the rule is comparable to another in the
                # grammar.
                return
            else:
                # This is not strictly true for DictationGrammar, but still holds
                # for match_rules and output from the compile methods.
                raise GrammarError(
                    "JSGF grammars cannot have multiple rules with "
                    "the same name")

        # If the rule is not a dictation rule, add it to the JSGF only grammar and
        # the original rule map.
        if not dictation_in_expansion(rule.expansion):
            self._jsgf_only_grammar.add_rule(rule)
            self._original_rule_map[rule] = rule
            return

        # Check if the rule is a SequenceRule already and do a few things with it.
        if isinstance(rule, SequenceRule):
            if not rule.current_is_dictation_only:
                # The sequence starts with a JSGF only rule and can be
                # spoken like a normal rule
                self._jsgf_only_grammar.add_rule(rule)
            else:
                self._dictation_rules.append(rule)
            self._original_rule_map[rule] = rule
            return

        # Expand the rule's expansion into a list of 1 or more expansions.
        expanded = expand_dictation_expansion(rule.expansion)

        # Otherwise create new rules from the resulting expansions and add each to
        # either dictation_rules or _jsgf_only_grammar
        for i, x in enumerate(expanded):
            if len(expanded) == 1:
                # No need to use different names in this case
                new_name = rule.name
            else:
                new_name = "%s_%d" % (rule.name, i)
            if not dictation_in_expansion(x):
                r = Rule(new_name, rule.visible, x)

                # Add this rule to the JSGF only grammar
                self._jsgf_only_grammar.add_rule(r)

                # Keep track of the relationship between the original rule and its
                # expanded rules
                self._original_rule_map[r] = rule
            else:
                seq_rule = SequenceRule(new_name, rule.visible, x)
                self._original_rule_map[seq_rule] = rule

                if not seq_rule.current_is_dictation_only:
                    # The sequence starts with a JSGF only rule and can be
                    # spoken like a normal rule
                    self._jsgf_only_grammar.add_rule(seq_rule)
                else:
                    self._dictation_rules.append(seq_rule)

    def get_original_rule(self, rule):
        """
        Get the original rule used to generate a rule from find_matching_rules.
        :type rule: Rule
        :return: Rule
        """
        return self._original_rule_map[rule]

    def get_generated_rules(self, rule):
        """
        Get a generator yielding the rules generated from a rule added to this
        grammar.
        :type rule: Rule
        """
        for k, v in list(self._original_rule_map.items()):
            if v is rule:
                yield k

    def remove_rule(self, rule, ignore_dependent=False):
        # Find the rules generated from this rule and remove them wherever they are
        # as well as the original rule
        if isinstance(rule, string_types):
            rule_name = rule
        else:
            rule_name = rule.name

        for k, v in list(self._original_rule_map.items()):
            if v.name == rule_name:
                self._original_rule_map.pop(k)

                if k in self._dictation_rules:
                    self._dictation_rules.remove(k)

                elif k in self._jsgf_only_grammar.match_rules:
                    self._jsgf_only_grammar.remove_rule(k, ignore_dependent)

    def _compile(self, compile_as_root_grammar):
        """
        Internal method to compile the grammar.
        :type compile_as_root_grammar: bool
        :return: str
        """
        self.rearrange_rules()

        try:
            # Compile the grammar
            if compile_as_root_grammar:
                result = self._jsgf_only_grammar.compile_as_root_grammar()
            else:
                result = self._jsgf_only_grammar.compile()

            # Check for compiled rules
            rule_pattern = re.compile("(public )?<.+> = .+;")

            # If there are none, set result to "".
            if not rule_pattern.search(result):
                result = ""
        except GrammarError as e:
            if len(self._dictation_rules) > 0:
                return ""
            else:
                raise GrammarError("no Dictation rules and JSGF only grammar "
                                   "failed to compile with error: '%s'" % e)
        return result

    def compile(self):
        return self._compile(False)

    def compile_as_root_grammar(self):
        return self._compile(True)

    def rearrange_rules(self):
        """
        Move SequenceRules in this grammar between the dictation rules list and
        the internal grammar used for JSGF only rules depending on whether a
        SequenceRule's current expansion is dictation only or not.
        """
        for rule in tuple(self._jsgf_only_grammar.match_rules):
            if not isinstance(rule, SequenceRule):
                continue
            if rule.current_is_dictation_only:
                self._jsgf_only_grammar.remove_rule(rule)
                self._dictation_rules.append(rule)

        for rule in tuple(self._dictation_rules):
            if not rule.current_is_dictation_only:
                self._jsgf_only_grammar.add_rule(rule)
                self._dictation_rules.remove(rule)

    def reset_sequence_rules(self):
        """
        Reset each SequenceRule in this grammar so that they can accept matches
        again.
        """
        for r in self._jsgf_only_grammar.match_rules + self._dictation_rules:
            if isinstance(r, SequenceRule):
                r.restart_sequence()

        self.rearrange_rules()

    def find_matching_rules(self, speech, advance_sequence_rules=True):
        """
        Find each visible rule passed to the grammar that matches the 'speech'
        string. Also set matches for the original rule.
        :type speech: str
        :param advance_sequence_rules: whether to call set_next() for successful
        sequence rule matches.
        :return: iterable
        """
        # Match against each match rule and remove any rules that didn't match
        result = self.match_rules
        for rule in tuple(result):
            if not rule.matches(speech):
                result.remove(rule)

        # Get the original rule for each rule in the result and ensure that their
        # current_match values reflect the generated rules' values.
        for rule in result:
            original = self.get_original_rule(rule)
            if isinstance(rule, SequenceRule):
                SequenceRule.graft_sequence_matches(rule, original.expansion)

                # Progress to the next expansion if required
                if rule.has_next_expansion and advance_sequence_rules:
                    rule.set_next()

            else:
                original.matches(rule.expansion.current_match)

        # Move SequenceRules between _dictation_rules and _jsgf_only_grammar as
        # required
        self.rearrange_rules()

        return result