Skip to content

Commit

Permalink
[BUGFIX] Fix failing specs on item repeats
Browse files Browse the repository at this point in the history
Fix #7

* Use a C or Java extension
* Kill a lot of yucky, poorly performing and error prone code
  • Loading branch information
benlangfeld committed Mar 4, 2013
1 parent d3bf708 commit 1630fce
Show file tree
Hide file tree
Showing 16 changed files with 152 additions and 683 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,6 +1,7 @@
.DS_Store
*.gem
.bundle
*.bundle
*.jar
Gemfile.lock
pkg/*
spec/reports
Expand Down
3 changes: 3 additions & 0 deletions .travis.yml
Expand Up @@ -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"
4 changes: 4 additions & 0 deletions 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" }
Expand Down
16 changes: 14 additions & 2 deletions Rakefile
Expand Up @@ -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
42 changes: 42 additions & 0 deletions 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();
}

}
23 changes: 23 additions & 0 deletions 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;
}
}
7 changes: 7 additions & 0 deletions 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'
41 changes: 41 additions & 0 deletions 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);
}
17 changes: 0 additions & 17 deletions lib/ruby_speech/grxml/element.rb
Expand Up @@ -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
21 changes: 0 additions & 21 deletions lib/ruby_speech/grxml/item.rb
Expand Up @@ -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
18 changes: 7 additions & 11 deletions lib/ruby_speech/grxml/matcher.rb
@@ -1,3 +1,10 @@
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
Expand Down Expand Up @@ -104,17 +111,6 @@ def check_full_match(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
Expand Down
4 changes: 0 additions & 4 deletions lib/ruby_speech/grxml/one_of.rb
Expand Up @@ -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
4 changes: 0 additions & 4 deletions lib/ruby_speech/grxml/token.rb
Expand Up @@ -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
9 changes: 9 additions & 0 deletions ruby_speech.gemspec
Expand Up @@ -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"]
Expand All @@ -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

0 comments on commit 1630fce

Please sign in to comment.