Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0

Sunday, 3 February 2013

rspec and re-factoring cs_grammar

I have been taking another look a context sensitive L-Systems when I found this. Which kind of inspired me to have another look at my cs_lsysteme library, and I made use of the rspec framework to do some re-factoring of my cs_grammar lib. Here is the updated rspec that describes the expected performance (context no longer scrolls around the 'production string' in either direction, this behaviour matches lmusej):-
require 'cs_grammar'

IGNORE='[]'

describe "Grammar" do
  context "given axiom 'baaaaaa'" do
    context "with function b<a" do
      describe "#generate(x)" do
        it "should equal 'baaaaaa' at zero iteration" do
          Grammar.new('baaaaaa', {'b' => 'a', 'b<a' => 'b'}).generate(0).should == 'baaaaaa'
        end
        it "should equal 'abaaaaa' after one iteration" do
          Grammar.new('baaaaaa', {'b' => 'a', 'b<a' => 'b'}).generate(1).should == 'abaaaaa'
        end
        it "should equal 'aabaaaa' after one iteration" do
          Grammar.new('abaaaaa', {'b' => 'a', 'b<a' => 'b'}).generate(1).should == 'aabaaaa'
        end
        it "should equal 'aabaaaa' after two iterations" do
          Grammar.new('baaaaaa', {'b' => 'a', 'b<a' => 'b'}).generate(2).should == 'aabaaaa'
        end
      end
    end
    context "with function b<a and ignore []" do
      describe "#generate(x)" do
        it "should equal 'baaa[a]aa' at zero iteration" do
          Grammar.new('baaa[a]aa', {'b' => 'a', 'b<a' => 'b'}, IGNORE).generate(0).should == 'baaa[a]aa'
        end
        it "should equal 'abaaaaa' after one iteration" do
          Grammar.new('baaa[a]aa', {'b' => 'a', 'b<a' => 'b'}, IGNORE).generate(1).should == 'abaa[a]aa'
        end
        it "should equal 'aabaaaa' after one iteration" do
          Grammar.new('abaa[a]aa', {'b' => 'a', 'b<a' => 'b'}, IGNORE).generate(1).should == 'aaba[a]aa'
        end
        it "should equal 'aabaaaa' after two iterations" do
          Grammar.new('baaa[a]aa', {'b' => 'a', 'b<a' => 'b'}, IGNORE).generate(2).should == 'aaba[a]aa'
        end
      end
    end
    context "with function a>b" do
      describe "#generate(x)" do
        it "should equal 'aaaaaaab' at zero iteration" do
          Grammar.new('aaaaaaab', {'b' => 'a', 'a>b' => 'b'}).generate(0).should == 'aaaaaaab'
        end
        it "should equal 'aaaaaba' after one iteration" do
          Grammar.new('aaaaaab', {'b' => 'a', 'a>b' => 'b'}).generate(1).should == 'aaaaaba'
        end
        it "should equal 'aaaabaa' after one iteration" do
          Grammar.new('aaaaaba', {'b' => 'a', 'a>b' => 'b'}).generate(1).should == 'aaaabaa'
        end
        it "should equal 'aaaabaa' after two iterations" do
          Grammar.new('aaaaaab', {'b' => 'a', 'a>b' => 'b'}).generate(2).should == 'aaaabaa'
        end
      end
    end
  end
end

Here is the revised library code:-
######################################
# cs_grammar.rb a context sensitive
# 1-L lsystem grammar for 
# ruby/ruby-processing
# by Martin Prout (January 2013)
######################################



##################################
# The grammar class stores rules
# in two Hashes, one for cs rules,
# one for context free rules. Rules
# are filtered on input, and context
# is checked using get_rule in production
##################################

class Grammar
  attr_accessor :lh_context
  attr_reader :axiom, :context, :no_context, :idx, :ignore, :lh_buf
  def initialize(axiom, rules, ignore = '')
    @axiom = axiom
    @no_context = {}
    @context = {}
    @ignore = ignore
    @lh_context = false
    rules.each_pair do |pair|
      add_rule pair[0], pair[1]
    end
  end

  def add_rule(pre, rule)
    if pre.length == 3
      if pre[1] == '<'
        @lh_context = true
        @context[pre[2]] = pre
      elsif pre[1] == '>'
        @context[pre[0]] = pre
      end
      @no_context[pre] = rule # key length == 3
    elsif pre.length == 1
      @no_context[pre] = rule # key length == 1
    else
      print "unrecognized grammar '#{pre}'"
    end
  end

  def generate(repeat = 0) # repeat iteration grammar rules
    prod = axiom
    repeat.times do
      prod = new_production(prod)
    end
    return prod
  end


  def new_production prod  # single iteration grammar rules
    @idx = 0
    @lh_buf = [] if lh_context  # create buffer to store lefthand context
    prod.gsub!(/./) do |ch|
      if lh_context
        lh_buf << ch unless ignore.include? ch # store lh context
      end
      get_rule(prod, ch)
    end
  end

  def get_rule prod, ch
    rule = ch # default is return original character as rule (no change)
    @idx += 1 # increment the index of axiom/production as a side effect
    if (context.has_key?(ch))
      if context[ch][1] == '<'
        cs_char = context[ch][0]
        rule = no_context[context[ch]] if cs_char == lh_buf[lh_buf.length - 2] # use context lh sensitive rule
      elsif context[ch][1] == '>'
        cs_char = context[ch][2]
        rule = no_context[context[ch]] if cs_char == get_rh_context(prod[idx .. prod.length]) # use context rh sensitive rule
      end
    else
      rule = no_context[ch] if no_context.has_key?(ch) # context free rule if it exists
    end
    return rule
  end

  def get_rh_context prod
    index = 0
    if ignore != ''
      while (ignore.include? prod[index])
        index += 1
      end
    end
    return prod[index]
  end
end


No comments:

Post a Comment

Followers

Blog Archive

About Me

My photo
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2