Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'feature/reliable_dtmf_matcher' into develop

  • Loading branch information...
commit db681fc4f38ccea49feecc39fa682afa4aedcbf1 2 parents 5bb5bfc + 1630fce
@benlangfeld authored
View
3  .gitignore
@@ -1,6 +1,7 @@
.DS_Store
*.gem
-.bundle
+*.bundle
+*.jar
Gemfile.lock
pkg/*
spec/reports
View
3  .travis.yml
@@ -7,5 +7,8 @@ rvm:
- rbx-19mode
- ruby-head
+before_install:
+ - sudo apt-get install libpcre3 libpcre3-dev
+
notifications:
irc: "irc.freenode.org#adhearsion-dev"
View
4 Guardfile
@@ -1,3 +1,7 @@
+guard 'rake', :task => 'compile' do
+ watch(%r{^ext/(.+)\.c$})
+end
+
guard 'rspec', :cli => '--format documentation' do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
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
16 Rakefile
@@ -15,8 +15,20 @@ RSpec::Core::RakeTask.new(:rcov) do |spec|
spec.rspec_opts = '--color'
end
-task :default => :spec
-task :ci => ['ci:setup:rspec', :spec]
+task :default => [:compile, :spec]
+task :ci => ['ci:setup:rspec', :compile, :spec]
require 'yard'
YARD::Rake::YardocTask.new
+
+if RUBY_PLATFORM =~ /java/
+ require 'rake/javaextensiontask'
+ Rake::JavaExtensionTask.new 'ruby_speech' do |ext|
+ ext.lib_dir = 'lib/ruby_speech'
+ end
+else
+ require 'rake/extensiontask'
+ Rake::ExtensionTask.new 'ruby_speech' do |ext|
+ ext.lib_dir = 'lib/ruby_speech'
+ end
+end
View
42 ext/ruby_speech/RubySpeechGRXMLMatcher.java
@@ -0,0 +1,42 @@
+package com.benlangfeld.ruby_speech;
+
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.anno.JRubyClass;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.Visibility;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.javasupport.util.RuntimeHelpers;
+
+import java.util.regex.*;
+
+@JRubyClass(name="RubySpeech::GRXML::Matcher")
+public class RubySpeechGRXMLMatcher extends RubyObject {
+
+ public RubySpeechGRXMLMatcher(final Ruby runtime, RubyClass rubyClass) {
+ super(runtime, rubyClass);
+ }
+
+ @JRubyMethod(visibility=Visibility.PRIVATE)
+ public IRubyObject check_potential_match(ThreadContext context, IRubyObject buffer)
+ {
+ Ruby runtime = context.getRuntime();
+
+ IRubyObject regex = getInstanceVariable("@regex");
+
+ Pattern p = Pattern.compile(regex.toString());
+ Matcher m = p.matcher(buffer.toString());
+
+ if (m.matches()) {
+ } else if (m.hitEnd()) {
+ RubyModule potential_match = runtime.getClassFromPath("RubySpeech::GRXML::PotentialMatch");
+ return RuntimeHelpers.invoke(context, potential_match, "new");
+ }
+ return runtime.getNil();
+ }
+
+}
View
23 ext/ruby_speech/RubySpeechService.java
@@ -0,0 +1,23 @@
+package com.benlangfeld.ruby_speech;
+
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.runtime.load.BasicLibraryService;
+
+public class RubySpeechService implements BasicLibraryService {
+ public boolean basicLoad(Ruby ruby) {
+ RubyModule ruby_speech = ruby.defineModule("RubySpeech");
+ RubyModule grxml = ruby_speech.defineModuleUnder("GRXML");
+ RubyClass matcher = grxml.defineClassUnder("Matcher", ruby.getObject(), new ObjectAllocator() {
+ public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
+ return new RubySpeechGRXMLMatcher(runtime, rubyClass);
+ }
+ });
+ matcher.defineAnnotatedMethods(RubySpeechGRXMLMatcher.class);
+ return true;
+ }
+}
View
7 ext/ruby_speech/extconf.rb
@@ -0,0 +1,7 @@
+require 'mkmf'
+
+$LIBS << " -lpcre"
+
+abort "-----\n#{lib} is missing.\n-----" unless find_header('pcre.h')
+
+create_makefile 'ruby_speech'
View
41 ext/ruby_speech/ruby_speech.c
@@ -0,0 +1,41 @@
+#include "ruby.h"
+#include "pcre.h"
+#include <stdio.h>
+
+static VALUE method_check_potential_match(VALUE self, VALUE buffer)
+{
+ int erroffset = 0;
+ const char *errptr = "";
+ int options = 0;
+ VALUE regex_string = rb_funcall(rb_iv_get(self, "@regex"), rb_intern("to_s"), 0);
+ const char *regex = StringValueCStr(regex_string);
+
+ pcre *compiled_regex = pcre_compile(regex, options, &errptr, &erroffset, NULL);
+
+ int result = 0;
+ int ovector[30];
+ int workspace[1024];
+ char *input = StringValueCStr(buffer);
+ result = pcre_dfa_exec(compiled_regex, NULL, input, strlen(input), 0, PCRE_PARTIAL,
+ ovector, sizeof(ovector) / sizeof(ovector[0]),
+ workspace, sizeof(workspace) / sizeof(workspace[0]));
+ pcre_free(compiled_regex);
+
+ if (result == PCRE_ERROR_PARTIAL) {
+ VALUE RubySpeech = rb_const_get(rb_cObject, rb_intern("RubySpeech"));
+ VALUE GRXML = rb_const_get(RubySpeech, rb_intern("GRXML"));
+ VALUE PotentialMatch = rb_const_get(GRXML, rb_intern("PotentialMatch"));
+
+ return rb_class_new_instance(0, NULL, PotentialMatch);
+ }
+ return Qnil;
+}
+
+void Init_ruby_speech()
+{
+ VALUE RubySpeech = rb_define_module("RubySpeech");
+ VALUE GRXML = rb_define_module_under(RubySpeech, "GRXML");
+ VALUE Matcher = rb_define_class_under(GRXML, "Matcher", rb_cObject);
+
+ rb_define_method(Matcher, "check_potential_match", method_check_potential_match, 1);
+}
View
1  lib/ruby_speech/grxml.rb
@@ -14,6 +14,7 @@ module GRXML
end
autoload :Match
+ autoload :Matcher
autoload :NoMatch
autoload :PotentialMatch
View
17 lib/ruby_speech/grxml/element.rb
@@ -24,23 +24,6 @@ def self.module
def regexp_content # :nodoc:
children.map(&:regexp_content).join
end
-
- def potential_match?(other)
- false
- end
-
- def max_input_length
- 0
- end
-
- def longest_potential_match(input)
- input.dup.tap do |longest_input|
- begin
- return longest_input if potential_match? longest_input
- longest_input.chop!
- end until longest_input.length.zero?
- end
- end
end # Element
end # GRXML
end # RubySpeech
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
21 lib/ruby_speech/grxml/item.rb
@@ -139,27 +139,6 @@ def regexp_content # :nodoc:
super
end
end
-
- def potential_match?(other)
- tokens = children
- return false if other.length > max_input_length
- other.chars.each_with_index do |digit, index|
- index -= tokens.size until index < tokens.size if repeat
- return false unless tokens[index].potential_match?(digit)
- end
- true
- end
-
- def max_input_length # :nodoc:
- case repeat
- when Range
- children.size * repeat.max
- when Integer
- children.size * repeat
- else
- children.size
- end
- end
end # Item
end # GRXML
end # RubySpeech
View
129 lib/ruby_speech/grxml/matcher.rb
@@ -0,0 +1,129 @@
+require 'ruby_speech/ruby_speech'
+
+if RUBY_PLATFORM =~ /java/
+ require 'jruby'
+ com.benlangfeld.ruby_speech.RubySpeechService.new.basicLoad(JRuby.runtime)
+end
+
+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 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
4 lib/ruby_speech/grxml/one_of.rb
@@ -26,10 +26,6 @@ def <<(arg)
def regexp_content # :nodoc:
"(#{children.map(&:regexp_content).join '|'})"
end
-
- def potential_match?(input)
- children.any? { |c| c.potential_match? input }
- end
end # OneOf
end # GRXML
end # RubySpeech
View
4 lib/ruby_speech/grxml/token.rb
@@ -26,10 +26,6 @@ def normalize_whitespace
def regexp_content # :nodoc:
Regexp.escape content
end
-
- def potential_match?(other)
- other == content
- end
end # Token
end # GRXML
end # RubySpeech
View
9 ruby_speech.gemspec
@@ -18,6 +18,13 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
+ if RUBY_PLATFORM =~ /java/
+ s.platform = "java"
+ s.files << "lib/ruby_speech/ruby_speech.jar"
+ else
+ s.extensions = ['ext/ruby_speech/extconf.rb']
+ end
+
s.add_runtime_dependency %q<niceogiri>, ["~> 1.1", ">= 1.1.1"]
s.add_runtime_dependency %q<nokogiri>, ["~> 1.5", ">= 1.5.6"]
s.add_runtime_dependency %q<activesupport>, [">= 3.0.7"]
@@ -32,4 +39,6 @@ Gem::Specification.new do |s|
s.add_development_dependency %q<guard>, [">= 0.9.0"]
s.add_development_dependency %q<guard-rspec>, [">= 0"]
s.add_development_dependency %q<ruby_gntp>, [">= 0"]
+ s.add_development_dependency %q<guard-rake>, [">= 0"]
+ s.add_development_dependency %q<rake-compiler>, [">= 0"]
end
View
528 spec/ruby_speech/grxml/grammar_spec.rb
@@ -336,534 +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, 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 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, 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 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
- pending
- 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
385 spec/ruby_speech/grxml/item_spec.rb
@@ -187,391 +187,6 @@ module GRXML
lambda { subject << Token.new }.should_not raise_error
end
end
-
- describe "#potential_match?" do
- subject { Item.new }
-
- before do
- tokens.each { |token| subject << token }
- subject.repeat = repeat if repeat
- end
-
- context "with a single token of '6'" do
- let(:tokens) { [Token.new << '6'] }
-
- context "with no repeat" do
- let(:repeat) { nil }
-
- it "should be true for '6'" do
- subject.potential_match?('6').should be true
- end
-
- %w{5 55 65 66}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
-
- context "with an absolute repeat of 3" do
- let(:repeat) { 3 }
-
- %w{6 66 666}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 55 65 6666}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
-
- context "with a range repeat of 0..2" do
- let(:repeat) { 0..2 }
-
- it "should be true for ''" do
- subject.potential_match?('').should be true
- end
-
- %w{6 66}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 55 65 666}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
-
- context "with a minimum repeat of 2" do
- let(:repeat) { 2..Item::Inf }
-
- %w{6 66 666 6666 66666}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 55 65}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
- end
-
- context "with a collection of two tokens of '6' and '7'" do
- let(:tokens) { [Token.new << '6', Token.new << '7'] }
-
- context "with no repeat" do
- let(:repeat) { nil }
-
- %w{6 67}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 55 65 66 676}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
-
- context "with an absolute repeat of 3" do
- let(:repeat) { 3 }
-
- %w{6 67 676 6767 67676 676767}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 57 66 677 5767 67677 676766 6767676}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
-
- context "with a range repeat of 0..2" do
- let(:repeat) { 0..2 }
-
- it "should be true for ''" do
- subject.potential_match?('').should be true
- end
-
- %w{6 67 676 6767}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 57 66 677 5767 67676 67677 676766 6767676}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
-
- context "with a minimum repeat of 2" do
- let(:repeat) { 2..Item::Inf }
-
- %w{6 67 676 6767 67676 676767 67676767}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 57 66 677 5767 67677 676766}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
- end
-
- context "with a nested item" do
- let(:repeat) { nil }
- let(:tokens) { [Item.new << (Token.new << '6') << (Token.new << '6')] }
-
- before do
- tokens.each { |token| token.repeat = nested_repeat if nested_repeat }
- end
-
- context "with no repeat" do
- before { pending }
- let(:nested_repeat) { nil }
-
- %w{6 66}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 55 65 666}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
-
- context "with an absolute repeat of 3" do
- before { pending }
- let(:nested_repeat) { 3 }
-
- %w{6 66 666}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 55 6666}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
-
- context "with a range repeat of 0..2" do
- before { pending }
- let(:nested_repeat) { 0..2 }
-
- it "should be true for ''" do
- subject.potential_match?('').should be true
- end
-
- %w{6 67 676 6767}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 57 66 677 5767 67676 67677 676766 6767676}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
-
- context "with a minimum repeat of 2" do
- before { pending }
- let(:nested_repeat) { 2..Item::Inf }
-
- %w{6 67 676 6767 67676 676767 67676767}.each do |input|
- it "should be true for '#{input}'" do
- subject.potential_match?(input).should be true
- end
- end
-
- %w{5 57 66 677 5767 67677 676766}.each do |input|
- it "should be false for '#{input}'" do
- subject.potential_match?(input).should be false
- end
- end
- end
- end
- end
-
- describe "#longest_potential_match" do
- subject { Item.new }
-
- before do
- tokens.each { |token| subject << token }
- subject.repeat = repeat if repeat
- end
-
- context "with a single token of '6'" do
- let(:tokens) { [Token.new << '6'] }
-
- context "with no repeat" do
- let(:repeat) { nil }
-
- %w{6 65 6776}.each do |input|
- it "should be '6' for '#{input}'" do
- subject.longest_potential_match(input).should == '6'
- end
- end
-
- %w{5 7 55 56}.each do |input|
- it "should be '' for '#{input}'" do
- subject.longest_potential_match(input).should == ''
- end
- end
- end
-
- context "with an absolute repeat of 3" do
- let(:repeat) { 3 }
-
- {
- '6' => '6',
- '66' => '66',
- '666' => '666',
- '6666' => '666',
- '66666' => '666'
- }.each do |input, match|
- it "should be '#{match}' for '#{input}'" do
- subject.longest_potential_match(input).should == match
- end
- end
- end
-
- context "with a range repeat of 0..2" do
- let(:repeat) { 0..2 }
-
- {
- '6' => '6',
- '66' => '66',
- '666' => '66',
- '6666' => '66'
- }.each do |input, match|
- it "should be '#{match}' for '#{input}'" do
- subject.longest_potential_match(input).should == match
- end
- end
- end
-
- context "with a minimum repeat of 2" do
- let(:repeat) { 2..Item::Inf }
-
- {
- '6' => '6',
- '66' => '66',
- '666' => '666',
- '6666' => '6666',
- '6'*100 => '6'*100
- }.each do |input, match|
- it "should be '#{match}' for '#{input}'" do
- subject.longest_potential_match(input).should == match
- end
- end
- end
- end
-
- context "with a collection of two tokens of '6' and '7'" do
- let(:tokens) { [Token.new << '6', Token.new << '7'] }
-
- context "with no repeat" do
- let(:repeat) { nil }
-
- {
- '6' => '6',
- '66' => '6',
- '67' => '67',
- '676' => '67',
- '6767' => '67'
- }.each do |input, match|
- it "should be '#{match}' for '#{input}'" do
- subject.longest_potential_match(input).should == match
- end
- end
- end
-
- context "with an absolute repeat of 3" do
- let(:repeat) { 3 }
-
- {
- '6' => '6',
- '66' => '6',
- '67' => '67',
- '676' => '676',
- '6767' => '6767',
- '67676' => '67676',
- '676767' => '676767',
- '6767676' => '676767'
- }.each do |input, match|
- it "should be '#{match}' for '#{input}'" do
- subject.longest_potential_match(input).should == match
- end
- end
- end
-
- context "with a range repeat of 0..2" do
- let(:repeat) { 0..2 }
-
- {
- '6' => '6',
- '66' => '6',
- '67' => '67',
- '676' => '676',
- '6767' => '6767',
- '67676' => '6767',
- '676767' => '6767'
- }.each do |input, match|
- it "should be '#{match}' for '#{input}'" do
- subject.longest_potential_match(input).should == match
- end
- end
- end
-
- context "with a minimum repeat of 2" do
- let(:repeat) { 2..Item::Inf }
-
- {
- '6' => '6',
- '66' => '6',
- '67' => '67',
- '676' => '676',
- '6767' => '6767',
- '67676' => '67676',
- '676767' => '676767',
- '6767676' => '6767676',
- '67'*100 => '67'*100
- }.each do |input, match|
- it "should be '#{match}' for '#{input}'" do
- subject.longest_potential_match(input).should == match
- end
- end
- end
- end
- end
end # Item
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