Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions documentation/test_grammar_elements_compound_doctest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,31 @@ therefore have three elements, even when "there" is not spoken. ::
[u'hello', None, u'universe']
>>> tester.recognize("hello galaxy")
RecognitionFailure


Inline repetition
----------------------------------------------------------------------------

Repeated extras can be expressed directly in the compound spec using
open-ended brace ranges. Repeated extras are returned as lists when used
from a value function. ::

>>> element = Compound(
... "test <food>{1,}",
... extras=[Choice("food", {"apple": "good", "burger": "bad"})],
... value_func=lambda node, extras: extras["food"],
... )
>>> tester = ElementTester(element)
>>> tester.recognize("test apple burger")
['good', 'bad']

Zero-or-more repetition returns an empty list when nothing is spoken. ::

>>> element = Compound(
... "test <food>{0,}",
... extras=[Choice("food", {"apple": "good", "burger": "bad"})],
... value_func=lambda node, extras: extras["food"],
... )
>>> tester = ElementTester(element)
>>> tester.recognize("test")
[]
26 changes: 25 additions & 1 deletion dragonfly/engines/backend_kaldi/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,31 @@ def compile_element(self, element, *args, **kwargs):
def _compile_sequence(self, element, src_state, dst_state, grammar, kaldi_rule, fst):
src_state = self.add_weight_linkage(src_state, dst_state, self.get_weight(element), fst)
children = element.children
is_repetition = isinstance(element, elements_.Repetition)
if is_repetition and element.unbounded:
current_src_state = src_state
for unused_index in range(max(element.min - 1, 0)):
next_state = fst.add_state()
self.compile_element(element._child, current_src_state,
next_state, grammar, kaldi_rule, fst)
current_src_state = next_state

loop_src_state = fst.add_state()
loop_dst_state = fst.add_state()
if element.min == 0:
fst.add_arc(current_src_state, dst_state, None)
fst.add_arc(current_src_state, loop_src_state, None)
self.compile_element(element._child, loop_src_state, loop_dst_state,
grammar, kaldi_rule, fst)
if not fst.has_eps_path(loop_src_state, loop_dst_state,
self._eps_like_nonterms):
fst.add_arc(loop_dst_state, loop_src_state, fst.eps_disambig,
fst.eps)
fst.add_arc(loop_dst_state, dst_state, None)
return
raise CompilerError("Cannot compile unbounded repetition whose "
"child can match empty")

# Optimize for special lengths
if len(children) == 0:
fst.add_arc(src_state, dst_state, None)
Expand All @@ -239,7 +264,6 @@ def _compile_sequence(self, element, src_state, dst_state, grammar, kaldi_rule,

else: # len(children) >= 2:
# Handle Repetition elements differently as a special case
is_repetition = isinstance(element, elements_.Repetition)
if is_repetition and element.optimize:
# Repetition...
# Insert new states, so back arc only affects child
Expand Down
25 changes: 24 additions & 1 deletion dragonfly/engines/backend_natlink/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,34 @@ def _compile_rule(self, rule, compiler):
#-----------------------------------------------------------------------
# Methods for compiling elements.

def _compile_unbounded_repetition(self, element, compiler):
if element.min > 1:
compiler.start_sequence()
for unused_index in range(element.min - 1):
self.compile_element(element._child, compiler)

if element.min == 0:
compiler.start_optional()

compiler.start_repetition()
self.compile_element(element._child, compiler)
compiler.end_repetition()

if element.min == 0:
compiler.end_optional()

if element.min > 1:
compiler.end_sequence()

def _compile_sequence(self, element, compiler):
children = element.children
is_rep = isinstance(element, elements_.Repetition)
if is_rep and element.unbounded:
self._compile_unbounded_repetition(element, compiler)
return

if len(children) > 1:
# Compile Sequence and Repetition elements differently.
is_rep = isinstance(element, elements_.Repetition)
if is_rep and element.optimize:
compiler.start_repetition()
self.compile_element(children[0], compiler)
Expand Down
25 changes: 24 additions & 1 deletion dragonfly/engines/backend_sapi5/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

from dragonfly.engines.base import CompilerBase, CompilerError
from dragonfly.grammar.rule_base import Rule
from dragonfly.grammar.elements_basic import Literal
from dragonfly.grammar.elements_basic import Literal, Repetition


#---------------------------------------------------------------------------
Expand Down Expand Up @@ -135,8 +135,31 @@ def _compile_rule(self, rule, grammar, grammar_handle):
#-----------------------------------------------------------------------
# Methods for compiling elements.

@trace_compile
def _compile_unbounded_repetition(self, element, src_state, dst_state,
grammar, grammar_handle):
current_src_state = src_state
for unused_index in range(max(element.min - 1, 0)):
next_state = current_src_state.Rule.AddState()
self.compile_element(element._child, current_src_state, next_state,
grammar, grammar_handle)
current_src_state = next_state

loop_dst_state = current_src_state.Rule.AddState()
if element.min == 0:
current_src_state.AddWordTransition(dst_state, '')
self.compile_element(element._child, current_src_state, loop_dst_state,
grammar, grammar_handle)
loop_dst_state.AddWordTransition(current_src_state, '')
loop_dst_state.AddWordTransition(dst_state, '')

@trace_compile
def _compile_sequence(self, element, src_state, dst_state, grammar, grammar_handle):
if isinstance(element, Repetition) and element.unbounded:
self._compile_unbounded_repetition(element, src_state, dst_state,
grammar, grammar_handle)
return

states = [src_state.Rule.AddState() for i in range(len(element.children)-1)]
states.insert(0, src_state)
states.append(dst_state)
Expand Down
28 changes: 28 additions & 0 deletions dragonfly/engines/backend_sphinx/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,30 @@ def compile_element(self, element, *args, **kwargs):
" for element type %s."
% (self, element))

def _compile_unbounded_repetition(self, element, repeat_class, *args,
**kwargs):
repeated_child = repeat_class(
self.compile_element(element._child, *args, **kwargs)
)

if element.min == 0:
return jsgf.OptionalGrouping(repeated_child)
if element.min == 1:
return repeated_child

children = [
self.compile_element(element._child, *args, **kwargs)
for unused_index in range(element.min - 1)
]
children.append(repeated_child)
return jsgf.Sequence(*children)

def _compile_repetition(self, element, *args, **kwargs):
if element.unbounded:
return self._compile_unbounded_repetition(
element, jsgf.Repeat, *args, **kwargs
)

# Compile the first element only; pyjsgf doesn't support limits on
# repetition (yet).
children = element.children
Expand Down Expand Up @@ -249,6 +272,11 @@ def __init__(self, engine):
# Methods for compiling elements.

def _compile_repetition(self, element, *args, **kwargs):
if element.unbounded:
return self._compile_unbounded_repetition(
element, PatchedRepeat, *args, **kwargs
)

# Compile the first element only; pyjsgf doesn't support limits on
# repetition (yet).
children = element.children
Expand Down
Loading