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
3 changes: 2 additions & 1 deletion documentation/ccr.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Continous Command Recognition (CCR)
One of dragonfly's most powerful features is continuous command recognition
(CCR), that is commands that can be spoken together without pausing. This is
done through use of a :class:`dragonfly.grammar.element_basic.Repetition`
rule element. There is a mini-demo of continuous command recognition
or :class:`dragonfly.grammar.element_basic.OneOrMore` rule element. There is
a mini-demo of continuous command recognition
`on YouTube <https://www.youtube.com/watch?v=g3c5H7sAbBQ>`__. There are also
a few projects using dragonfly which make writing CCR rules easier:

Expand Down
6 changes: 6 additions & 0 deletions documentation/elements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ Repetition class
.. autoclass:: dragonfly.grammar.elements_basic.Repetition
:members: dependencies, gstring, decode, value, children, get_repetitions

OneOrMore class
----------------------------------------------------------------------------
.. autoclass:: dragonfly.grammar.elements_basic.OneOrMore
:members: dependencies, gstring, decode, value, children,
get_repetitions, default_max

Literal class
----------------------------------------------------------------------------
.. autoclass:: dragonfly.grammar.elements_basic.Literal
Expand Down
41 changes: 37 additions & 4 deletions documentation/test_grammar_elements_basic_doctest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ Basic usage::
Exact number of repetitions::

>>> seq = Sequence([Literal("hello"), Literal("world")])
>>> rep = Repetition(seq, min=3, max=None, optimize=False)
>>> rep = Repetition(seq, min=3, max=3, optimize=False)
>>> test_rep = ElementTester(rep)
>>> test_rep.recognize("hello world")
RecognitionFailure
Expand All @@ -189,12 +189,45 @@ Exact number of repetitions::
>>> test_rep.recognize("hello world hello world hello world hello world")
RecognitionFailure

min must be less than max::
min must be less than or equal to max::

>>> rep = Repetition(Literal("hello"), min=3, max=3, optimize=False)
>>> rep = Repetition(Literal("hello"), min=4, max=3, optimize=False)
Traceback (most recent call last):
...
AssertionError: min must be less than max
AssertionError: min must be less than or equal to max


OneOrMore element classes
============================================================================

Basic usage::

>>> # OneOrMore is given a dragonfly element, in this case a Sequence.
>>> seq = Sequence([Literal("hello"), Literal("world")])
>>> # OneOrMore is a type of Repetition element.
>>> rep = OneOrMore(seq)
>>> test_rep = ElementTester(rep)
>>> test_rep.recognize("hello world")
[[u'hello', u'world']]
>>> test_rep.recognize("hello world hello world")
[[u'hello', u'world'], [u'hello', u'world']]
>>> # Incomplete recognitions result in recognition failure.
>>> test_rep.recognize("hello universe")
RecognitionFailure
>>> test_rep.recognize("hello world hello universe")
RecognitionFailure

Large number of repetitions::

>>> # Too many recognitions also result in recognition failure.
>>> test_rep.recognize(" ".join(["hello world"] * 17))
RecognitionFailure
>>> # Further repetitions may be matched by adjusting the class member.
>>> OneOrMore.default_max = 20
>>> rep = OneOrMore(seq)
>>> test_rep = ElementTester(rep)
>>> test_rep.recognize(" ".join(["hello world"] * 17))
[[u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world'], [u'hello', u'world']]


Modifier element class
Expand Down
2 changes: 1 addition & 1 deletion documentation/test_recobs_doctest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Simple literal element recognitions::

Integer element recognitions::

>>> test_int = ElementTester(Integer(min=1, max=100))
>>> test_int = ElementTester(Integer(min=1, max=99))
>>> test_int.recognize("seven")
7
>>> test_recobs.waiting, test_recobs.words
Expand Down
6 changes: 3 additions & 3 deletions dragonfly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@
from .grammar.rule_compound import CompoundRule
from .grammar.rule_mapping import MappingRule
from .grammar.elements import (ElementBase, Sequence, Alternative,
Optional, Repetition, Literal,
Optional, Repetition, OneOrMore, Literal,
ListRef, DictListRef, Dictation, Modifier,
RuleRef, RuleWrap, Compound, Choice,
Empty, Impossible)
RuleRef, RuleWrap, Compound, Choice, Empty,
Impossible)

from .grammar.context import Context, AppContext, FuncContext
from .grammar.list import ListBase, List, DictList
Expand Down
1 change: 1 addition & 0 deletions dragonfly/grammar/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
Alternative = basic_.Alternative
Optional = basic_.Optional
Repetition = basic_.Repetition
OneOrMore = basic_.OneOrMore
Literal = basic_.Literal
RuleRef = basic_.RuleRef
Rule = basic_.RuleRef # For backwards compatibility.
Expand Down
80 changes: 59 additions & 21 deletions dragonfly/grammar/elements_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
wrapper around a child element which makes the child element optional
- :class:`Repetition` --
repetition of a child element
- :class:`OneOrMore` --
one or more repetitions of a child element
- :class:`Literal` --
literal word which must be said exactly by the speaker as given
- :class:`RuleRef` --
Expand Down Expand Up @@ -570,34 +572,34 @@ class Repetition(Sequence):
be recognized (inclusive); may be 0
- *max* (*int*, default: *None*) --
the maximum number of times that the child element must
be recognized (exclusive!); if *None*, the child element must be
recognized exactly *min* times (i.e. *max = min + 1*)
be recognized (inclusive); if *None*, the child element may be
recognized up to *min + 1* times (i.e. *max = min + 1*)
- *name* (*str*, default: *None*) --
the name of this element
- *default* (*object*, default: *None*) --
the default value used if this element is optional and wasn't
spoken
- *optimize* (*bool*, default: *True*) --
whether the engine's compiler should attempt to compile the element
optimally
whether the engine's compiler should attempt to compile the
element optimally

For a recognition to match, the child element must be recognized at
least *min* times and strictly less than *max* times.
least *min* times and strictly less than or equal to *max* times.

Examples:
- *Repetition(child, min=2, max=5)* -- child 2, 3, or 4 times
- *Repetition(child, min=0, max=3)* -- child 0, 1, or 2 times
- *Repetition(child, max=3)* -- child 1 or 2 times
- *Repetition(child, min=1, max=2)* -- child exactly once
- *Repetition(child, min=1)* -- child exactly once
- *Repetition(child)* -- child exactly once

If the *optimize* argument is set to *True*, the engine's compiler may
attempt to ignore the *min* and *max* limits to reduce grammar
- *Repetition(child, min=2, max=4)* -- child 2, 3, or 4 times
- *Repetition(child, min=0, max=2)* -- child 0, 1, or 2 times
- *Repetition(child, max=2)* -- child 1 or 2 times
- *Repetition(child, min=1, max=1)* -- child exactly once
- *Repetition(child, min=1)* -- child 1 or 2 times
- *Repetition(child)* -- child 1 or 2 times

If the *optimize* argument is set to *True*, the engine's compiler
may attempt to ignore the *min* and *max* limits to reduce grammar
complexity. Not all engines support this, and some engines may only
support some rule structures. Regardless, if the number of repetitions
recognized is less than the *min* value -- or equal to or more than the
*max* value -- the rule will still fail to match.
support some rule structures. Regardless, if the number of
repetitions recognized is less than the *min* value -- or equal to
or more than the *max* value -- the rule will still fail to match.

"""

Expand All @@ -610,15 +612,15 @@ def __init__(self, child, min=1, max=None, name=None, default=None,
" ElementBase instance." % self)
assert isinstance(min, six.integer_types)
assert max is None or isinstance(max, six.integer_types)
assert max is None or min < max, "min must be less than max"
assert max is None or min <= max, "min must be less than or equal to max"

self._child = child
self._min = min
if max is None: self._max = min + 1
else: self._max = max
self._optimize = optimize

optional_length = self._max - self._min - 1
optional_length = self._max - self._min
if optional_length > 0:
element = Optional(child)
for index in range(optional_length-1):
Expand All @@ -644,8 +646,8 @@ def __init__(self, child, min=1, max=None, name=None, default=None,
max = property(
lambda self: self._max,
doc="The maximum number of times that the child element must be "
"recognized; if *None*, the child element must be "
"recognized exactly *min* times (i.e. *max = min + 1*). "
"recognized; if *None*, the child element may be "
"recognized up to *min + 1* times (i.e. *max = min + 1*). "
"(Read-only)"
)

Expand Down Expand Up @@ -717,6 +719,42 @@ def value(self, node):
return [r.value() for r in repetitions]


#---------------------------------------------------------------------------

class OneOrMore(Repetition):
"""
Element class representing one or more repetitions of one child
element.

Constructor arguments:
- *child* (*ElementBase*) --
the child element of this element
- *name* (*str*, default: *None*) --
the name of this element
- *default* (*object*, default: *None*) --
the default value used if this element is optional and wasn't
spoken

This class is a sub-class of :class:`Repetition`. For a recognition
to match, the child element must be recognized one or more times --
i.e. Kleene plus behavior.

**Note**: If more than sixteen repetitions of the child element are
given, this element class will fail to match the recognition and
cause an error. In the event that this occurs, the *default_max*
class member should be increased to match a larger number of
repetitions.

"""

#: Default *max* parameter to pass to the Repetition superclass.
default_max = 16

def __init__(self, child, name=None, default=None):
Repetition.__init__(self, child, min=1, max=self.default_max,
name=name, default=default, optimize=True)


#---------------------------------------------------------------------------

class Literal(ElementBase):
Expand Down
11 changes: 11 additions & 0 deletions dragonfly/language/base/integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

"""

from six import integer_types

from dragonfly.language.loader import language
from dragonfly.grammar.elements import (Alternative, Sequence, Optional,
Compound, ListRef, RuleWrap)
Expand Down Expand Up @@ -60,6 +62,15 @@ def __init__(self, name=None, min=None, max=None, default=None,
self._set_content(language.IntegerContent)
self._builders = self._content.builders

assert isinstance(min, integer_types), "min must be a number"
assert isinstance(max, integer_types), "max must be a number"
assert min <= max, "min must be less than or equal to max"

# Make the *max* argument behave inclusively.
# Note: This is an easier change than modifying the internal integer
# classes.
max = max + 1

self._min = min; self._max = max
children = self._build_children(min, max)
Alternative.__init__(self, children, name=name, default=default)
Expand Down
5 changes: 5 additions & 0 deletions dragonfly/language/base/integer_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@

#---------------------------------------------------------------------------
# Numeric element builder classes.
#
# Note: The classes in this file treat the *max* argument as exclusive,
# meaning that build_element() methods will return element trees for
# recognizing integers up to max - 1.


class IntBuilderBase(object):

Expand Down
34 changes: 17 additions & 17 deletions dragonfly/test/test_language_en_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ def _build_element(self):
]


class Limit3to14TestCase(ElementTestCase):
""" Verify integer limits of range 3 -- 14. """
class Limit3to13TestCase(ElementTestCase):
""" Verify integer limits of range 3 -- 13. """
def _build_element(self):
return Integer(content=IntegerContent, min=3, max=14)
return Integer(content=IntegerContent, min=3, max=13)
input_output = [
("oh", RecognitionFailure),
("zero", RecognitionFailure),
Expand All @@ -96,10 +96,10 @@ def _build_element(self):
]


class Limit23to47TestCase(ElementTestCase):
""" Verify integer limits of range 23 -- 47. """
class Limit23to46TestCase(ElementTestCase):
""" Verify integer limits of range 23 -- 46. """
def _build_element(self):
return Integer(content=IntegerContent, min=23, max=47)
return Integer(content=IntegerContent, min=23, max=46)
input_output = [
("twenty two", RecognitionFailure),
("twenty three", 23),
Expand All @@ -108,10 +108,10 @@ def _build_element(self):
]


class Limit230to350TestCase(ElementTestCase):
""" Verify integer limits of range 230 -- 350. """
class Limit230to349TestCase(ElementTestCase):
""" Verify integer limits of range 230 -- 349. """
def _build_element(self):
return Integer(content=IntegerContent, min=230, max=350)
return Integer(content=IntegerContent, min=230, max=349)
input_output = [
("two hundred twenty nine", RecognitionFailure),
("two hundred thirty", 230),
Expand All @@ -125,10 +125,10 @@ def _build_element(self):
]


class Limit351TestCase(ElementTestCase):
""" Verify integer limits of range up to 351. """
class Limit350TestCase(ElementTestCase):
""" Verify integer limits of range up to 350. """
def _build_element(self):
return Integer(content=IntegerContent, min=230, max=351)
return Integer(content=IntegerContent, min=230, max=350)
input_output = [
("three hundred forty nine", 349),
("three hundred fifty", 350),
Expand All @@ -137,10 +137,10 @@ def _build_element(self):
]


class Limit352TestCase(ElementTestCase):
""" Verify integer limits of range up to 352. """
class Limit351TestCase(ElementTestCase):
""" Verify integer limits of range up to 351. """
def _build_element(self):
return Integer(content=IntegerContent, min=230, max=352)
return Integer(content=IntegerContent, min=230, max=351)
input_output = [
("three hundred forty nine", 349),
("three hundred fifty", 350),
Expand All @@ -153,7 +153,7 @@ def _build_element(self):
class DigitsTestCase(ElementTestCase):
""" Verify that digits between 0 and 10 can be recognized. """
def _build_element(self):
return Digits(content=DigitsContent, min=1, max=6)
return Digits(content=DigitsContent, min=1, max=5)
input_output = [
("zero", [0]),
("oh", [0]),
Expand Down Expand Up @@ -182,7 +182,7 @@ def _build_element(self):
class DigitsAsIntTestCase(ElementTestCase):
""" Verify that Digits used with as_int=True gives correct values. """
def _build_element(self):
return Digits(content=DigitsContent, min=1, max=6, as_int=True)
return Digits(content=DigitsContent, min=1, max=5, as_int=True)
input_output = [
("zero", 0),
("oh", 0),
Expand Down