Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

[CS] Move matching from the grammar model into a Method Object

Separates responsibility for evaluating a match to a grammar from the grammar itself
  • Loading branch information...
commit d3bf708a9059aee7681eb1359a82229aa364f1d4 1 parent 73ae1f1
@benlangfeld authored
View
90 README.md
@@ -108,96 +108,64 @@ which becomes
#### Grammar matching
-It is possible to match some arbitrary input against a GRXML grammar. In order to do so, certain normalization routines should first be run on the grammar in order to prepare it for matching. These are reference inlining, tokenization and whitespace normalization, and are described [in the SRGS spec](http://www.w3.org/TR/speech-grammar/#S2.1). This process will transform the above grammar like so:
+It is possible to match some arbitrary input against a GRXML grammar, like so:
```ruby
-grammy.inline!
-grammy.tokenize!
-grammy.normalize_whitespace
-```
+require 'ruby_speech'
-```xml
-<grammar xmlns="http://www.w3.org/2001/06/grammar" version="1.0" xml:lang="en-US" mode="dtmf" root="pin">
- <rule id="pin" scope="public">
- <one-of>
- <item>
- <item repeat="4">
- <one-of>
- <item>
- <token>0</token>
- </item>
- <item>
- <token>1</token>
- </item>
- <item>
- <token>2</token>
- </item>
- <item>
- <token>3</token>
- </item>
- <item>
- <token>4</token>
- </item>
- <item>
- <token>5</token>
- </item>
- <item>
- <token>6</token>
- </item>
- <item>
- <token>7</token>
- </item>
- <item>
- <token>8</token>
- </item>
- <item>
- <token>9</token>
- </item>
- </one-of>
- </item>
- <token>#</token>
- </item>
- <item>
- <token>*</token>
- <token>9</token>
- </item>
- </one-of>
- </rule>
-</grammar>
-```
+>> grammar = RubySpeech::GRXML.draw mode: :dtmf, root: 'pin' do
+ rule id: 'digit' do
+ one_of do
+ ('0'..'9').map { |d| item { d } }
+ end
+ end
-Matching against some sample input strings then returns the following results:
+ rule id: 'pin', scope: 'public' do
+ one_of do
+ item do
+ item repeat: '4' do
+ ruleref uri: '#digit'
+ end
+ "#"
+ end
+ item do
+ "* 9"
+ end
+ end
+ end
+end
-```ruby
->> subject.match '*9'
+matcher = RubySpeech::GRXML::Matcher.new grammar
+
+>> matcher.match '*9'
=> #<RubySpeech::GRXML::Match:0x00000100ae5d98
@mode = :dtmf,
@confidence = 1,
@utterance = "*9",
@interpretation = "*9"
>
->> subject.match '1234#'
+>> matcher.match '1234#'
=> #<RubySpeech::GRXML::Match:0x00000100b7e020
@mode = :dtmf,
@confidence = 1,
@utterance = "1234#",
@interpretation = "1234#"
>
->> subject.match '5678#'
+>> matcher.match '5678#'
=> #<RubySpeech::GRXML::Match:0x00000101218688
@mode = :dtmf,
@confidence = 1,
@utterance = "5678#",
@interpretation = "5678#"
>
->> subject.match '1111#'
+>> matcher.match '1111#'
=> #<RubySpeech::GRXML::Match:0x000001012f69d8
@mode = :dtmf,
@confidence = 1,
@utterance = "1111#",
@interpretation = "1111#"
>
->> subject.match '111'
+>> matcher.match '111'
=> #<RubySpeech::GRXML::NoMatch:0x00000101371660>
```
View
1  lib/ruby_speech/grxml.rb
@@ -14,6 +14,7 @@ module GRXML
end
autoload :Match
+ autoload :Matcher
autoload :NoMatch
autoload :PotentialMatch
View
103 lib/ruby_speech/grxml/grammar.rb
@@ -149,99 +149,6 @@ def normalize_whitespace
end
end
- ##
- # Checks the grammar for a match against an input string
- #
- # @param [String] other the input string to check for a match with the grammar
- #
- # @return [NoMatch, Match] depending on the result of a match attempt. If a match can be found, it will be returned with appropriate mode/confidence/utterance and interpretation attributes
- #
- # @example A grammar that takes a 4 digit pin terminated by hash, or the *9 escape sequence
- # ```ruby
- # grammar = RubySpeech::GRXML.draw :mode => :dtmf, :root => 'pin' do
- # rule :id => 'digit' do
- # one_of do
- # ('0'..'9').map { |d| item { d } }
- # end
- # end
- #
- # rule :id => 'pin', :scope => 'public' do
- # one_of do
- # item do
- # item :repeat => '4' do
- # ruleref :uri => '#digit'
- # end
- # "#"
- # end
- # item do
- # "\* 9"
- # end
- # end
- # end
- # end
- #
- # >> subject.match '*9'
- # => #<RubySpeech::GRXML::Match:0x00000100ae5d98
- # @mode = :dtmf,
- # @confidence = 1,
- # @utterance = "*9",
- # @interpretation = "*9"
- # >
- # >> subject.match '1234#'
- # => #<RubySpeech::GRXML::Match:0x00000100b7e020
- # @mode = :dtmf,
- # @confidence = 1,
- # @utterance = "1234#",
- # @interpretation = "1234#"
- # >
- # >> subject.match '111'
- # => #<RubySpeech::GRXML::PotentialMatch:0x00000101371660>
- #
- # >> subject.match '11111'
- # => #<RubySpeech::GRXML::NoMatch:0x00000101371936>
- #
- # ```
- #
- def match(other)
- other = other.dup
- regex = to_regexp
- return check_for_potential_match(other) if regex == //
- match = regex.match other
- return check_for_potential_match(other) unless match
-
- Match.new :mode => mode,
- :confidence => dtmf? ? 1 : 0,
- :utterance => other,
- :interpretation => interpret_utterance(other)
- end
-
- def check_for_potential_match(other)
- potential_match?(other) ? PotentialMatch.new : NoMatch.new
- end
-
- def potential_match?(other)
- root_rule.children.each do |token|
- return true if other.length.zero?
- longest_potential_match = token.longest_potential_match other
- return false if longest_potential_match.length.zero?
- other.gsub! /^#{Regexp.escape longest_potential_match}/, ''
- end
- other.length.zero?
- end
-
- ##
- # Converts the grammar into a regular expression for matching
- #
- # @return [Regexp] a regular expression which is equivalent to the grammar
- #
- def to_regexp
- /^#{regexp_content.join}$/
- end
-
- def regexp_content
- root_rule.children.map &:regexp_content
- end
-
def dtmf?
mode == :dtmf
end
@@ -270,16 +177,6 @@ def has_matching_root_rule?
!root || root_rule
end
- def interpret_utterance(utterance)
- conversion = Hash.new { |hash, key| hash[key] = key }
- conversion['*'] = 'star'
- conversion['#'] = 'pound'
-
- utterance.chars.inject [] do |array, digit|
- array << "dtmf-#{conversion[digit]}"
- end.join ' '
- end
-
def split_tokens(element)
element.to_s.split(/(\".*\")/).reject(&:empty?).map do |string|
match = string.match /^\"(.*)\"$/
View
133 lib/ruby_speech/grxml/matcher.rb
@@ -0,0 +1,133 @@
+module RubySpeech
+ module GRXML
+ class Matcher
+
+ BLANK_REGEX = //.freeze
+
+ attr_reader :grammar, :regex
+
+ def initialize(grammar)
+ @grammar = grammar
+ prepare_grammar
+ @regex = /^#{regexp_content.join}$/
+ end
+
+ ##
+ # Checks the grammar for a match against an input string
+ #
+ # @param [String] other the input string to check for a match with the grammar
+ #
+ # @return [NoMatch, Match] depending on the result of a match attempt. If a match can be found, it will be returned with appropriate mode/confidence/utterance and interpretation attributes
+ #
+ # @example A grammar that takes a 4 digit pin terminated by hash, or the *9 escape sequence
+ # ```ruby
+ # grammar = RubySpeech::GRXML.draw :mode => :dtmf, :root => 'pin' do
+ # rule :id => 'digit' do
+ # one_of do
+ # ('0'..'9').map { |d| item { d } }
+ # end
+ # end
+ #
+ # rule :id => 'pin', :scope => 'public' do
+ # one_of do
+ # item do
+ # item :repeat => '4' do
+ # ruleref :uri => '#digit'
+ # end
+ # "#"
+ # end
+ # item do
+ # "\* 9"
+ # end
+ # end
+ # end
+ # end
+ #
+ # matcher = RubySpeech::GRXML::Matcher.new grammar
+ #
+ # >> matcher.match '*9'
+ # => #<RubySpeech::GRXML::Match:0x00000100ae5d98
+ # @mode = :dtmf,
+ # @confidence = 1,
+ # @utterance = "*9",
+ # @interpretation = "*9"
+ # >
+ # >> matcher.match '1234#'
+ # => #<RubySpeech::GRXML::Match:0x00000100b7e020
+ # @mode = :dtmf,
+ # @confidence = 1,
+ # @utterance = "1234#",
+ # @interpretation = "1234#"
+ # >
+ # >> matcher.match '5678#'
+ # => #<RubySpeech::GRXML::Match:0x00000101218688
+ # @mode = :dtmf,
+ # @confidence = 1,
+ # @utterance = "5678#",
+ # @interpretation = "5678#"
+ # >
+ # >> matcher.match '1111#'
+ # => #<RubySpeech::GRXML::Match:0x000001012f69d8
+ # @mode = :dtmf,
+ # @confidence = 1,
+ # @utterance = "1111#",
+ # @interpretation = "1111#"
+ # >
+ # >> matcher.match '111'
+ # => #<RubySpeech::GRXML::NoMatch:0x00000101371660>
+ # ```
+ #
+ def match(buffer)
+ buffer = buffer.dup
+
+ return check_potential_match(buffer) if regex == BLANK_REGEX
+
+ check_full_match(buffer) || check_potential_match(buffer) || NoMatch.new
+ end
+
+ private
+
+ def prepare_grammar
+ grammar.inline!
+ grammar.tokenize!
+ grammar.normalize_whitespace
+ end
+
+ def check_full_match(buffer)
+ match = regex.match buffer
+
+ return unless match
+
+ Match.new :mode => grammar.mode,
+ :confidence => grammar.dtmf? ? 1 : 0,
+ :utterance => buffer,
+ :interpretation => interpret_utterance(buffer)
+ end
+
+ def check_potential_match(buffer)
+ grammar.root_rule.children.each do |token|
+ p "Checking buffer #{buffer} against token #{token} which has a longest potential match #{token.longest_potential_match(buffer)}"
+ break if buffer.length.zero?
+ longest_potential_match = token.longest_potential_match buffer
+ return if longest_potential_match.length.zero?
+ buffer.gsub! /^#{Regexp.escape longest_potential_match}/, ''
+ end
+ buffer.length.zero? ? PotentialMatch.new : nil
+ end
+
+ def regexp_content
+ grammar.root_rule.children.map &:regexp_content
+ end
+
+ def interpret_utterance(utterance)
+ conversion = Hash.new { |hash, key| hash[key] = key }
+ conversion['*'] = 'star'
+ conversion['#'] = 'pound'
+
+ utterance.chars.inject [] do |array, digit|
+ array << "dtmf-#{conversion[digit]}"
+ end.join ' '
+ end
+ end
+ end
+end
View
639 spec/ruby_speech/grxml/grammar_spec.rb
@@ -336,645 +336,6 @@ def single_rule_grammar(content = [])
grammar.should == normalized_grammar
end
end
-
- describe "matching against an input string" do
- before do
- subject.inline!
- subject.tokenize!
- subject.normalize_whitespace
- end
-
- context "with a grammar that takes a single specific digit" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digit' do
- rule :id => 'digit' do
- '6'
- end
- end
- end
-
- it "should match '6'" do
- input = '6'
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '6',
- :interpretation => 'dtmf-6'
- subject.match(input).should == expected_match
- input.should == '6'
- end
-
- %w{1 2 3 4 5 7 8 9 10 66 26 61}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes two specific digits" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- '5 6'
- end
- end
- end
-
- it "should match '56'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '56',
- :interpretation => 'dtmf-5 dtmf-6'
- subject.match('56').should == expected_match
- end
-
- it "should potentially match '5'" do
- input = '5'
- subject.match(input).should == GRXML::PotentialMatch.new
- input.should == '5'
- end
-
- %w{* *7 #6 6* 1 2 3 4 6 7 8 9 10 65 57 46 26 61}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes star and a digit" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- '* 6'
- end
- end
- end
-
- it "should match '*6'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '*6',
- :interpretation => 'dtmf-star dtmf-6'
- subject.match('*6').should == expected_match
- end
-
- it "should potentially match '*'" do
- subject.match('*').should == GRXML::PotentialMatch.new
- end
-
- %w{*7 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes hash and a digit" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- '# 6'
- end
- end
- end
-
- it "should match '#6'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '#6',
- :interpretation => 'dtmf-pound dtmf-6'
- subject.match('#6').should == expected_match
- end
-
- it "should potentially match '#'" do
- subject.match('#').should == GRXML::PotentialMatch.new
- end
-
- %w{* *6 #7 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes two specific digits, via a ruleref, and whitespace normalization" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- ruleref :uri => '#star'
- '" 6 "'
- end
-
- rule :id => 'star' do
- '" * "'
- end
- end
- end
-
- it "should match '*6'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '*6',
- :interpretation => 'dtmf-star dtmf-6'
- subject.match('*6').should == expected_match
- end
-
- it "should potentially match '*'" do
- subject.match('*').should == GRXML::PotentialMatch.new
- end
-
- %w{*7 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a single digit alternative" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- one_of do
- item { '6' }
- item { '7' }
- end
- end
- end
- end
-
- it "should match '6'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '6',
- :interpretation => 'dtmf-6'
- subject.match('6').should == expected_match
- end
-
- it "should match '7'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '7',
- :interpretation => 'dtmf-7'
- subject.match('7').should == expected_match
- end
-
- %w{* # 1 2 3 4 5 8 9 10 66 26 61}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a double digit alternative" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- one_of do
- item do
- token { '6' }
- token { '5' }
- end
- item do
- token { '7' }
- token { '2' }
- end
- end
- end
- end
- end
-
- it "should match '65'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '65',
- :interpretation => 'dtmf-6 dtmf-5'
- subject.match('65').should == expected_match
- end
-
- it "should match '72'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '72',
- :interpretation => 'dtmf-7 dtmf-2'
- subject.match('72').should == expected_match
- end
-
- %w{6 7}.each do |input|
- it "should potentially match '#{input}'" do
- subject.match(input).should == GRXML::PotentialMatch.new
- end
- end
-
- %w{* # 1 2 3 4 5 8 9 10 66 26 61 75}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a triple digit alternative" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- one_of do
- item do
- token { '6' }
- token { '5' }
- token { '2' }
- end
- item do
- token { '7' }
- token { '2' }
- token { '8' }
- end
- end
- end
- end
- end
-
- it "should match '652'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '652',
- :interpretation => 'dtmf-6 dtmf-5 dtmf-2'
- subject.match('652').should == expected_match
- end
-
- it "should match '728'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '728',
- :interpretation => 'dtmf-7 dtmf-2 dtmf-8'
- subject.match('728').should == expected_match
- end
-
- %w{6 65 7 72}.each do |input|
- it "should potentially match '#{input}'" do
- subject.match(input).should == GRXML::PotentialMatch.new
- end
- end
-
- %w{* # 1 2 3 4 5 8 9 10 66 26 61 75 729 654}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes two specific digits with the second being an alternative" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- string '*'
- one_of do
- item { '6' }
- item { '7' }
- end
- end
- end
- end
-
- it "should match '*6'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '*6',
- :interpretation => 'dtmf-star dtmf-6'
- subject.match('*6').should == expected_match
- end
-
- it "should match '*7'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '*7',
- :interpretation => 'dtmf-star dtmf-7'
- subject.match('*7').should == expected_match
- end
-
- it "should potentially match '*'" do
- subject.match('*').should == GRXML::PotentialMatch.new
- end
-
- %w{*8 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes two specific digits with the first being an alternative" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- one_of do
- item { '6' }
- item { '7' }
- end
- string '*'
- end
- end
- end
-
- it "should match '6*'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '6*',
- :interpretation => 'dtmf-6 dtmf-star'
- subject.match('6*').should == expected_match
- end
-
- it "should match '7*'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '7*',
- :interpretation => 'dtmf-7 dtmf-star'
- subject.match('7*').should == expected_match
- end
-
- it "should potentially match '6'" do
- subject.match('6').should == GRXML::PotentialMatch.new
- end
-
- it "should potentially match '7'" do
- subject.match('7').should == GRXML::PotentialMatch.new
- end
-
- %w{8* 6# *6 *7 1 2 3 4 5 8 9 10 66 26 61}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a specific digit, followed by a specific digit repeated an exact number of times" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- string '1'
- item :repeat => 2 do
- '6'
- end
- end
- end
- end
-
- it "should match '166'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '166',
- :interpretation => 'dtmf-1 dtmf-6 dtmf-6'
- subject.match('166').should == expected_match
- end
-
- %w{1 16}.each do |input|
- it "should potentially match '#{input}'" do
- subject.match(input).should == GRXML::PotentialMatch.new
- end
- end
-
- %w{1666 16666 17}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a specific digit repeated an exact number of times, followed by a specific digit" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- item :repeat => 2 do
- '6'
- end
- string '1'
- end
- end
- end
-
- it "should match '661'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => '661',
- :interpretation => 'dtmf-6 dtmf-6 dtmf-1'
- subject.match('661').should == expected_match
- end
-
- %w{6 66}.each do |input|
- it "should potentially match '#{input}'" do
- subject.match(input).should == GRXML::PotentialMatch.new
- end
- end
-
- %w{61 6661 66661 71 771}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a specific digit, followed by a specific digit repeated within a range" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- string '1'
- item :repeat => 0..3 do
- '6'
- end
- end
- end
- end
-
- {
- '1' => 'dtmf-1',
- '16' => 'dtmf-1 dtmf-6',
- '166' => 'dtmf-1 dtmf-6 dtmf-6',
- '1666' => 'dtmf-1 dtmf-6 dtmf-6 dtmf-6'
- }.each_pair do |input, interpretation|
- it "should match '#{input}'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => input,
- :interpretation => interpretation
- subject.match(input).should == expected_match
- end
- end
-
- %w{6 66 666}.each do |input|
- it "should potentially match '#{input}'" do
- subject.match(input).should == GRXML::PotentialMatch.new
- end
- end
-
- %w{66661 71}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a a specific digit repeated within a range, followed by specific digit" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- item :repeat => 0..3 do
- '6'
- end
- string '1'
- end
- end
- end
-
- {
- '1' => 'dtmf-1',
- '61' => 'dtmf-6 dtmf-1',
- '661' => 'dtmf-6 dtmf-6 dtmf-1',
- '6661' => 'dtmf-6 dtmf-6 dtmf-6 dtmf-1'
- }.each_pair do |input, interpretation|
- it "should match '#{input}'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => input,
- :interpretation => interpretation
- subject.match(input).should == expected_match
- end
- end
-
- %w{6 66661 71}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a specific digit, followed by a specific digit repeated a minimum number of times" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- string '1'
- item :repeat => '2-' do
- '6'
- end
- end
- end
- end
-
- {
- '166' => 'dtmf-1 dtmf-6 dtmf-6',
- '1666' => 'dtmf-1 dtmf-6 dtmf-6 dtmf-6',
- '16666' => 'dtmf-1 dtmf-6 dtmf-6 dtmf-6 dtmf-6'
- }.each_pair do |input, interpretation|
- it "should match '#{input}'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => input,
- :interpretation => interpretation
- subject.match(input).should == expected_match
- end
- end
-
- %w{1 16}.each do |input|
- it "should potentially match '#{input}'" do
- subject.match(input).should == GRXML::PotentialMatch.new
- end
- end
-
- %w{7 17}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a specific digit repeated a minimum number of times, followed by a specific digit" do
- subject do
- GRXML.draw :mode => :dtmf, :root => 'digits' do
- rule :id => 'digits' do
- item :repeat => '2-' do
- '6'
- end
- string '1'
- end
- end
- end
-
- {
- '661' => 'dtmf-6 dtmf-6 dtmf-1',
- '6661' => 'dtmf-6 dtmf-6 dtmf-6 dtmf-1',
- '66661' => 'dtmf-6 dtmf-6 dtmf-6 dtmf-6 dtmf-1'
- }.each_pair do |input, interpretation|
- it "should match '#{input}'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => input,
- :interpretation => interpretation
- subject.match(input).should == expected_match
- end
- end
-
- %w{6 66}.each do |input|
- it "should potentially match '#{input}'" do
- subject.match(input).should == GRXML::PotentialMatch.new
- end
- end
-
- %w{7 71 61}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
-
- context "with a grammar that takes a 4 digit pin terminated by hash, or the *9 escape sequence" do
- subject do
- RubySpeech::GRXML.draw :mode => :dtmf, :root => 'pin' do
- rule :id => 'digit' do
- one_of do
- ('0'..'9').map { |d| item { d } }
- end
- end
-
- rule :id => 'pin', :scope => 'public' do
- one_of do
- item do
- item :repeat => '4' do
- ruleref :uri => '#digit'
- end
- "#"
- end
- item do
- "\* 9"
- end
- end
- end
- end
- end
-
- {
- '*9' => 'dtmf-star dtmf-9',
- '1234#' => 'dtmf-1 dtmf-2 dtmf-3 dtmf-4 dtmf-pound',
- '5678#' => 'dtmf-5 dtmf-6 dtmf-7 dtmf-8 dtmf-pound',
- '1111#' => 'dtmf-1 dtmf-1 dtmf-1 dtmf-1 dtmf-pound'
- }.each_pair do |input, interpretation|
- it "should match '#{input}'" do
- expected_match = GRXML::Match.new :mode => :dtmf,
- :confidence => 1,
- :utterance => input,
- :interpretation => interpretation
- subject.match(input).should == expected_match
- end
- end
-
- %w{* 1 12 123 1234}.each do |input|
- it "should potentially match '#{input}'" do
- subject.match(input).should == GRXML::PotentialMatch.new
- end
- end
-
- %w{11111 #1111 *7}.each do |input|
- it "should not match '#{input}'" do
- subject.match(input).should == GRXML::NoMatch.new
- end
- end
- end
- end
end # Grammar
end # GRXML
end # RubySpeech
View
644 spec/ruby_speech/grxml/matcher_spec.rb
@@ -0,0 +1,644 @@
+require 'spec_helper'
+
+module RubySpeech
+ module GRXML
+ describe Matcher do
+ let(:grammar) { nil }
+
+ subject { described_class.new grammar }
+
+ describe "matching against an input string" do
+ context "with a grammar that takes a single specific digit" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digit' do
+ rule :id => 'digit' do
+ '6'
+ end
+ end
+ end
+
+ it "should match '6'" do
+ input = '6'
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '6',
+ :interpretation => 'dtmf-6'
+ subject.match(input).should == expected_match
+ input.should == '6'
+ end
+
+ %w{1 2 3 4 5 7 8 9 10 66 26 61}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes two specific digits" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ '5 6'
+ end
+ end
+ end
+
+ it "should match '56'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '56',
+ :interpretation => 'dtmf-5 dtmf-6'
+ subject.match('56').should == expected_match
+ end
+
+ it "should potentially match '5'" do
+ input = '5'
+ subject.match(input).should == GRXML::PotentialMatch.new
+ input.should == '5'
+ end
+
+ %w{* *7 #6 6* 1 2 3 4 6 7 8 9 10 65 57 46 26 61}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes star and a digit" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ '* 6'
+ end
+ end
+ end
+
+ it "should match '*6'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '*6',
+ :interpretation => 'dtmf-star dtmf-6'
+ subject.match('*6').should == expected_match
+ end
+
+ it "should potentially match '*'" do
+ subject.match('*').should == GRXML::PotentialMatch.new
+ end
+
+ %w{*7 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes hash and a digit" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ '# 6'
+ end
+ end
+ end
+
+ it "should match '#6'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '#6',
+ :interpretation => 'dtmf-pound dtmf-6'
+ subject.match('#6').should == expected_match
+ end
+
+ it "should potentially match '#'" do
+ subject.match('#').should == GRXML::PotentialMatch.new
+ end
+
+ %w{* *6 #7 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes two specific digits, via a ruleref, and whitespace normalization" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ ruleref :uri => '#star'
+ '" 6 "'
+ end
+
+ rule :id => 'star' do
+ '" * "'
+ end
+ end
+ end
+
+ it "should match '*6'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '*6',
+ :interpretation => 'dtmf-star dtmf-6'
+ subject.match('*6').should == expected_match
+ end
+
+ it "should potentially match '*'" do
+ subject.match('*').should == GRXML::PotentialMatch.new
+ end
+
+ %w{*7 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a single digit alternative" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ one_of do
+ item { '6' }
+ item { '7' }
+ end
+ end
+ end
+ end
+
+ it "should match '6'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '6',
+ :interpretation => 'dtmf-6'
+ subject.match('6').should == expected_match
+ end
+
+ it "should match '7'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '7',
+ :interpretation => 'dtmf-7'
+ subject.match('7').should == expected_match
+ end
+
+ %w{* # 1 2 3 4 5 8 9 10 66 26 61}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a double digit alternative" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ one_of do
+ item do
+ token { '6' }
+ token { '5' }
+ end
+ item do
+ token { '7' }
+ token { '2' }
+ end
+ end
+ end
+ end
+ end
+
+ it "should match '65'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '65',
+ :interpretation => 'dtmf-6 dtmf-5'
+ subject.match('65').should == expected_match
+ end
+
+ it "should match '72'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '72',
+ :interpretation => 'dtmf-7 dtmf-2'
+ subject.match('72').should == expected_match
+ end
+
+ %w{6 7}.each do |input|
+ it "should potentially match '#{input}'" do
+ subject.match(input).should == GRXML::PotentialMatch.new
+ end
+ end
+
+ %w{* # 1 2 3 4 5 8 9 10 66 26 61 75}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a triple digit alternative" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ one_of do
+ item do
+ token { '6' }
+ token { '5' }
+ token { '2' }
+ end
+ item do
+ token { '7' }
+ token { '2' }
+ token { '8' }
+ end
+ end
+ end
+ end
+ end
+
+ it "should match '652'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '652',
+ :interpretation => 'dtmf-6 dtmf-5 dtmf-2'
+ subject.match('652').should == expected_match
+ end
+
+ it "should match '728'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '728',
+ :interpretation => 'dtmf-7 dtmf-2 dtmf-8'
+ subject.match('728').should == expected_match
+ end
+
+ %w{6 65 7 72}.each do |input|
+ it "should potentially match '#{input}'" do
+ subject.match(input).should == GRXML::PotentialMatch.new
+ end
+ end
+
+ %w{* # 1 2 3 4 5 8 9 10 66 26 61 75 729 654}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes two specific digits with the second being an alternative" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ string '*'
+ one_of do
+ item { '6' }
+ item { '7' }
+ end
+ end
+ end
+ end
+
+ it "should match '*6'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '*6',
+ :interpretation => 'dtmf-star dtmf-6'
+ subject.match('*6').should == expected_match
+ end
+
+ it "should match '*7'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '*7',
+ :interpretation => 'dtmf-star dtmf-7'
+ subject.match('*7').should == expected_match
+ end
+
+ it "should potentially match '*'" do
+ subject.match('*').should == GRXML::PotentialMatch.new
+ end
+
+ %w{*8 #6 6* 1 2 3 4 5 6 7 8 9 10 66 26 61}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes two specific digits with the first being an alternative" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ one_of do
+ item { '6' }
+ item { '7' }
+ end
+ string '*'
+ end
+ end
+ end
+
+ it "should match '6*'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '6*',
+ :interpretation => 'dtmf-6 dtmf-star'
+ subject.match('6*').should == expected_match
+ end
+
+ it "should match '7*'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '7*',
+ :interpretation => 'dtmf-7 dtmf-star'
+ subject.match('7*').should == expected_match
+ end
+
+ it "should potentially match '6'" do
+ subject.match('6').should == GRXML::PotentialMatch.new
+ end
+
+ it "should potentially match '7'" do
+ subject.match('7').should == GRXML::PotentialMatch.new
+ end
+
+ %w{8* 6# *6 *7 1 2 3 4 5 8 9 10 66 26 61}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a specific digit, followed by a specific digit repeated an exact number of times" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ string '1'
+ item :repeat => 2 do
+ '6'
+ end
+ end
+ end
+ end
+
+ it "should match '166'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '166',
+ :interpretation => 'dtmf-1 dtmf-6 dtmf-6'
+ subject.match('166').should == expected_match
+ end
+
+ %w{1 16}.each do |input|
+ it "should potentially match '#{input}'" do
+ subject.match(input).should == GRXML::PotentialMatch.new
+ end
+ end
+
+ %w{1666 16666 17}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a specific digit repeated an exact number of times, followed by a specific digit" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ item :repeat => 2 do
+ '6'
+ end
+ string '1'
+ end
+ end
+ end
+
+ it "should match '661'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => '661',
+ :interpretation => 'dtmf-6 dtmf-6 dtmf-1'
+ subject.match('661').should == expected_match
+ end
+
+ %w{6 66}.each do |input|
+ it "should potentially match '#{input}'" do
+ subject.match(input).should == GRXML::PotentialMatch.new
+ end
+ end
+
+ %w{61 6661 66661 71 771}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a specific digit, followed by a specific digit repeated within a range" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ string '1'
+ item :repeat => 0..3 do
+ '6'
+ end
+ end
+ end
+ end
+
+ {
+ '1' => 'dtmf-1',
+ '16' => 'dtmf-1 dtmf-6',
+ '166' => 'dtmf-1 dtmf-6 dtmf-6',
+ '1666' => 'dtmf-1 dtmf-6 dtmf-6 dtmf-6'
+ }.each_pair do |input, interpretation|
+ it "should match '#{input}'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => input,
+ :interpretation => interpretation
+ subject.match(input).should == expected_match
+ end
+ end
+
+ %w{6 16666 17}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a a specific digit repeated within a range, followed by specific digit" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ item :repeat => 0..3 do
+ '6'
+ end
+ string '1'
+ end
+ end
+ end
+
+ {
+ '1' => 'dtmf-1',
+ '61' => 'dtmf-6 dtmf-1',
+ '661' => 'dtmf-6 dtmf-6 dtmf-1',
+ '6661' => 'dtmf-6 dtmf-6 dtmf-6 dtmf-1'
+ }.each_pair do |input, interpretation|
+ it "should match '#{input}'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => input,
+ :interpretation => interpretation
+ subject.match(input).should == expected_match
+ end
+ end
+
+ %w{6 66 666}.each do |input|
+ it "should potentially match '#{input}'" do
+ subject.match(input).should == GRXML::PotentialMatch.new
+ end
+ end
+
+ %w{66661 71}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a specific digit, followed by a specific digit repeated a minimum number of times" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ string '1'
+ item :repeat => '2-' do
+ '6'
+ end
+ end
+ end
+ end
+
+ {
+ '166' => 'dtmf-1 dtmf-6 dtmf-6',
+ '1666' => 'dtmf-1 dtmf-6 dtmf-6 dtmf-6',
+ '16666' => 'dtmf-1 dtmf-6 dtmf-6 dtmf-6 dtmf-6'
+ }.each_pair do |input, interpretation|
+ it "should match '#{input}'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => input,
+ :interpretation => interpretation
+ subject.match(input).should == expected_match
+ end
+ end
+
+ %w{1 16}.each do |input|
+ it "should potentially match '#{input}'" do
+ subject.match(input).should == GRXML::PotentialMatch.new
+ end
+ end
+
+ %w{7 17}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a specific digit repeated a minimum number of times, followed by a specific digit" do
+ let(:grammar) do
+ GRXML.draw :mode => :dtmf, :root => 'digits' do
+ rule :id => 'digits' do
+ item :repeat => '2-' do
+ '6'
+ end
+ string '1'
+ end
+ end
+ end
+
+ {
+ '661' => 'dtmf-6 dtmf-6 dtmf-1',
+ '6661' => 'dtmf-6 dtmf-6 dtmf-6 dtmf-1',
+ '66661' => 'dtmf-6 dtmf-6 dtmf-6 dtmf-6 dtmf-1'
+ }.each_pair do |input, interpretation|
+ it "should match '#{input}'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => input,
+ :interpretation => interpretation
+ subject.match(input).should == expected_match
+ end
+ end
+
+ %w{6 66}.each do |input|
+ it "should potentially match '#{input}'" do
+ subject.match(input).should == GRXML::PotentialMatch.new
+ end
+ end
+
+ %w{7 71 61}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+
+ context "with a grammar that takes a 4 digit pin terminated by hash, or the *9 escape sequence" do
+ let(:grammar) do
+ RubySpeech::GRXML.draw :mode => :dtmf, :root => 'pin' do
+ rule :id => 'digit' do
+ one_of do
+ ('0'..'9').map { |d| item { d } }
+ end
+ end
+
+ rule :id => 'pin', :scope => 'public' do
+ one_of do
+ item do
+ item :repeat => '4' do
+ ruleref :uri => '#digit'
+ end
+ "#"
+ end
+ item do
+ "\* 9"
+ end
+ end
+ end
+ end
+ end
+
+ {
+ '*9' => 'dtmf-star dtmf-9',
+ '1234#' => 'dtmf-1 dtmf-2 dtmf-3 dtmf-4 dtmf-pound',
+ '5678#' => 'dtmf-5 dtmf-6 dtmf-7 dtmf-8 dtmf-pound',
+ '1111#' => 'dtmf-1 dtmf-1 dtmf-1 dtmf-1 dtmf-pound'
+ }.each_pair do |input, interpretation|
+ it "should match '#{input}'" do
+ expected_match = GRXML::Match.new :mode => :dtmf,
+ :confidence => 1,
+ :utterance => input,
+ :interpretation => interpretation
+ subject.match(input).should == expected_match
+ end
+ end
+
+ %w{* 1 12 123 1234}.each do |input|
+ it "should potentially match '#{input}'" do
+ subject.match(input).should == GRXML::PotentialMatch.new
+ end
+ end
+
+ %w{11111 #1111 *7}.each do |input|
+ it "should not match '#{input}'" do
+ subject.match(input).should == GRXML::NoMatch.new
+ end
+ end
+ end
+ end
+ end # Grammar
+ end # GRXML
+end # RubySpeech
Please sign in to comment.
Something went wrong with that request. Please try again.