Skip to content

Commit

Permalink
[FEATURE] Basic matching to a grammar
Browse files Browse the repository at this point in the history
  • Loading branch information
benlangfeld committed Jan 1, 2012
1 parent d7f063a commit aee1b77
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 5 deletions.
3 changes: 3 additions & 0 deletions lib/ruby_speech/grxml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ module GRXML
autoload :Token
end

autoload :Match
autoload :NoMatch

InvalidChildError = Class.new StandardError

GRXML_NAMESPACE = 'http://www.w3.org/2001/06/grammar'
Expand Down
4 changes: 4 additions & 0 deletions lib/ruby_speech/grxml/element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ def self.module
alias_method :nokogiri_children, :children

include GenericElement

def regexp_content
children.map { |e| "(#{e.regexp_content})" }
end
end # Element
end # GRXML
end # RubySpeech
34 changes: 33 additions & 1 deletion lib/ruby_speech/grxml/grammar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Grammar < Element
# @return [String]
#
def mode
read_attr :mode
read_attr :mode, :to_sym
end

##
Expand Down Expand Up @@ -122,6 +122,38 @@ def normalize_whitespace
end
end

def match(other)
regex = to_regex
return NoMatch.new if regex == //
match = to_regex.match other
return NoMatch.new unless match

Match.new :mode => mode,
:confidence => dtmf? ? 1 : 0,
:utterance => other,
:interpretation => interpret_utterance(other)
end

def to_regex
/^#{regexp_content.join}$/
end

def regexp_content
root_rule.children.map { |e| "(#{e.regexp_content})" }
end

def dtmf?
mode == :dtmf
end

def voice?
mode == :voice
end

def interpret_utterance(utterance)
utterance
end

def <<(arg)
raise InvalidChildError, "A Grammar can only accept Rule and Tag as children" unless VALID_CHILD_TYPES.include? arg.class
super
Expand Down
16 changes: 16 additions & 0 deletions lib/ruby_speech/grxml/match.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module RubySpeech
module GRXML
class Match
attr_accessor :mode, :confidence, :utterance, :interpretation

def initialize(options = {})
options.each_pair { |k, v| self.send :"#{k}=", v }
end

def eql?(o)
o.is_a?(self.class) && [:mode, :confidence, :utterance, :interpretation].all? { |f| self.__send__(f) == o.__send__(f) }
end
alias :== :eql?
end
end
end
10 changes: 10 additions & 0 deletions lib/ruby_speech/grxml/no_match.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module RubySpeech
module GRXML
class NoMatch
def eql?(o)
o.is_a? self.class
end
alias :== :eql?
end
end
end
4 changes: 4 additions & 0 deletions lib/ruby_speech/grxml/one_of.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def <<(arg)
raise InvalidChildError, "A OneOf can only accept Item as children" unless VALID_CHILD_TYPES.include? arg.class
super
end

def regexp_content
children.join '|'
end
end # OneOf
end # GRXML
end # RubySpeech
4 changes: 4 additions & 0 deletions lib/ruby_speech/grxml/token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def <<(arg)
def normalize_whitespace
self.content = content.strip.squeeze ' '
end

def regexp_content
content.gsub '*', '\*'
end
end # Token
end # GRXML
end # RubySpeech
181 changes: 177 additions & 4 deletions spec/ruby_speech/grxml/grammar_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ module GRXML

describe "setting dtmf mode" do
subject { Grammar.new :mode => 'dtmf' }
its(:mode) { should == 'dtmf' }
its(:mode) { should == :dtmf }
its(:dtmf?) { should be true }
its(:voice?) { should be false }
end

describe "setting voice mode" do
subject { Grammar.new :mode => 'voice' }
its(:mode) { should == 'voice' }
subject { Grammar.new :mode => 'voice' }
its(:mode) { should == :voice }
its(:voice?) { should be true }
its(:dtmf?) { should be false }
end

it 'registers itself' do
Expand All @@ -44,7 +48,7 @@ module GRXML

its(:language) { pending; should == 'jp' }
its(:base_uri) { should == 'blah' }
its(:mode) { should == 'dtmf' }
its(:mode) { should == :dtmf }
its(:root) { should == 'main_rule' }
end

Expand Down Expand Up @@ -338,6 +342,175 @@ 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
expected_match = GRXML::Match.new :mode => :dtmf,
:confidence => 1,
:utterance => '6',
:interpretation => '6'
subject.match('6').should == expected_match
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 => '56'
subject.match('56').should == expected_match
end

%w{* *7 #6 6* 1 2 3 4 5 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 => '*6'
subject.match('*6').should == expected_match
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 => '#6'
subject.match('#6').should == expected_match
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 => '*6'
subject.match('*6').should == expected_match
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 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 => '*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 => '*7'
subject.match('*7').should == expected_match
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
end
end # Grammar
end # GRXML
end # RubySpeech
49 changes: 49 additions & 0 deletions spec/ruby_speech/grxml/match_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require 'spec_helper'

module RubySpeech
module GRXML
describe Match do
subject do
Match.new :mode => :dtmf,
:confidence => 1,
:utterance => '6',
:interpretation => 'foo'
end

its(:mode) { should == :dtmf }
its(:confidence) { should == 1 }
its(:utterance) { should == '6' }
its(:interpretation) { should == 'foo' }

describe "equality" do
it "should be equal when mode, confidence, utterance and interpretation are the same" do
Match.new(:mode => :dtmf, :confidence => 1, :utterance => '6', :interpretation => 'foo').should == Match.new(:mode => :dtmf, :confidence => 1, :utterance => '6', :interpretation => 'foo')
end

describe "when the mode is different" do
it "should not be equal" do
Match.new(:mode => :dtmf).should_not == Match.new(:mode => :speech)
end
end

describe "when the confidence is different" do
it "should not be equal" do
Match.new(:confidence => 1).should_not == Match.new(:confidence => 0)
end
end

describe "when the utterance is different" do
it "should not be equal" do
Match.new(:utterance => '6').should_not == Match.new(:utterance => 'foo')
end
end

describe "when the interpretation is different" do
it "should not be equal" do
Match.new(:interpretation => 'foo').should_not == Match.new(:interpretation => 'bar')
end
end
end
end
end
end
17 changes: 17 additions & 0 deletions spec/ruby_speech/grxml/no_match_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'spec_helper'

module RubySpeech
module GRXML
describe NoMatch do
describe "equality" do
it "should be equal to another NoMatch" do
NoMatch.new.should == NoMatch.new
end

it "should not equal a match" do
NoMatch.new.should_not == Match.new
end
end
end
end
end

0 comments on commit aee1b77

Please sign in to comment.