diff options
-rw-r--r-- | test/ruby/sentence.rb | 290 | ||||
-rw-r--r-- | test/ruby/test_assignment.rb | 2 | ||||
-rw-r--r-- | test/ruby/test_yield.rb | 4 |
3 files changed, 281 insertions, 15 deletions
diff --git a/test/ruby/sentence.rb b/test/ruby/sentence.rb index 3697fbea10..3eaaf9c11a 100644 --- a/test/ruby/sentence.rb +++ b/test/ruby/sentence.rb @@ -1,30 +1,204 @@ -# sentence +# == sentence library +# +# = Features +# +# * syntax based sentences generation +# * sentence operations such as substitution. +# +# = Example +# +# Some arithmetic expressions using "+", "-", "*" and "/" are generated as follows. +# +# require 'sentence' +# Sentence.each({ +# :exp => [["num"], +# [:exp, "+", :exp], +# [:exp, "-", :exp], +# [:exp, "*", :exp], +# [:exp, "/", :exp]] +# }, :exp, 2) {|sent| p sent } +# #=> +# #<Sentence: "num"> +# #<Sentence: ("num") "+" ("num")> +# #<Sentence: ("num") "+" (("num") "+" ("num"))> +# #<Sentence: ("num") "+" (("num") "-" ("num"))> +# #<Sentence: ("num") "+" (("num") "*" ("num"))> +# #<Sentence: ("num") "+" (("num") "/" ("num"))> +# #<Sentence: (("num") "+" ("num")) "+" ("num")> +# ... +# +# Sentence.each takes 3 arguments. +# The first argument is the syntax for the expressions. +# The second argument, :exp, is a generating nonterminal. +# The third argument, 2, limits derivation to restrict results finitely. +# +# Some arithmetic expressions including parenthesis can be generated as follows. +# +# synax = { +# :factor => [["n"], +# ["(", :exp, ")"]], +# :term => [[:factor], +# [:term, "*", :factor], +# [:term, "/", :factor]], +# :exp => [[:term], +# [:exp, "+", :term], +# [:exp, "-", :term]] +# } +# Sentence.each(syntax, :exp, 2) {|sent| p sent } +# #=> +# #<Sentence: (("n"))> +# #<Sentence: (("(" ((("n"))) ")"))> +# #<Sentence: (("(" ((("(" ((("n"))) ")"))) ")"))> +# #<Sentence: (("(" (((("n")) "*" ("n"))) ")"))> +# #<Sentence: (("(" (((("n")) "/" ("n"))) ")"))> +# #<Sentence: (("(" (((("n"))) "+" (("n"))) ")"))> +# #<Sentence: (("(" (((("n"))) "-" (("n"))) ")"))> +# #<Sentence: ((("n")) "*" ("n"))> +# #<Sentence: ((("n")) "*" ("(" ((("n"))) ")"))> +# ... +# +# Sentence#to_s can be used to concatenate strings +# in a sentence: +# +# Sentence.each(syntax, :exp, 2) {|sent| p sent.to_s } +# #=> +# "n" +# "(n)" +# "((n))" +# "(n*n)" +# "(n/n)" +# "(n+n)" +# "(n-n)" +# "n*n" +# "n*(n)" +# ... +# +# Sentence class represents a tree with string leaves. +# class Sentence + # _ary_ represents a tree. + # It should be a possibly nested array which contains strings. + # + # Note that _ary_ is not copied. + # Don't modify _ary_ after the sentence object is instantiated. + # + # Sentence.new(["a", "pen"]) + # #<Sentence: "a" "pen"> + # + # Sentence.new(["I", "have", ["a", "pen"]]) + # #<Sentence: "I" "have" ("a" "pen")> + # def initialize(ary) @sent = ary end + # returns a string which is concatenation of all strings. + # No separator is used. + # + # Sentence.new(["2", "+", "3"]).to_s + # "2+3" + # + # Sentence.new(["2", "+", ["3", "*", "5"]]).to_s + # "2+3*5" + # def to_s @sent.join('') end + # returns a string which is concatenation of all strings separated by _sep_. + # If _sep_ is not given, single space is used. + # + # Sentence.new(["I", "have", ["a", "pen"]]).join + # "I have a pen" + # + # Sentence.new(["I", "have", ["a", "pen"]]).join("/") + # "I/have/a/pen" + # + # Sentence.new(["a", [], "b"]).join("/") + # "a/b" + # + def join(sep=' ') + @sent.flatten.join(sep) + end + + # returns a tree as a nested array. + # + # Note that the result is not copied. + # Don't modify the result. + # + # Sentence.new([["foo", "bar"], "baz"]).to_a + # #=> [["foo", "bar"], "baz"] + # def to_a @sent end + # returns <i>i</i>th element as a sentence or string. + # + # s = Sentence.new([["foo", "bar"], "baz"]) + # s #=> #<Sentence: ("foo" "bar") "baz"> + # s[0] #=> #<Sentence: "foo" "bar"> + # s[1] #=> "baz" + # def [](i) - Sentence.new(@sent[i]) + e = @sent[i] + e.respond_to?(:to_ary) ? Sentence.new(e) : e + end + + # returns the number of top level elements. + # + # Sentence.new(%w[foo bar]).length + # #=> 2 + # + # Sentence.new([%w[2 * 7], "+", %w[3 * 5]]).length + # #=> 3 + # + def length + @sent.length end def inspect - "#<#{self.class}: #{@sent.inspect}>" + "#<#{self.class}: #{inner_inspect(@sent, '')}>" end + # :stopdoc: + def inner_inspect(ary, r) + first = true + ary.each {|obj| + r << ' ' if !first + first = false + if obj.respond_to? :to_ary + r << '(' + inner_inspect(obj, r) + r << ')' + else + r << obj.inspect + end + } + r + end + # :startdoc: + + # returns new sentence object which + # _target_ is substituted by the block. + # + # Sentence#subst invokes <tt>_target_ === _string_</tt> for each + # string in the sentence. + # The strings which === returns true are substituted by the block. + # The block is invoked with the substituting string. + # + # Sentence.new(%w[2 + 3]).subst("+") { "*" } + # #<Sentence: "2" "*" "3"> + # + # Sentence.new(%w[2 + 3]).subst(/\A\d+\z/) {|s| ((s.to_i)*2).to_s } + # #=> #<Sentence: "4" "+" "6"> + # def subst(target, &b) Sentence.new(subst_rec(@sent, target, &b)) end + # :stopdoc: def subst_rec(obj, target, &b) if obj.respond_to? :to_ary a = [] @@ -36,19 +210,25 @@ class Sentence obj end end + # :startdoc: + # find a subsentence and return it. + # The block is invoked for each subsentence in preorder manner. + # The first subsentence which the block returns true is returned. + # + # Sentence.new([%w[2 * 7], "+", %w[3 * 5]]).find_subtree {|s| s[1] == "*" } + # #=> #<Sentence: "2" "*" "7"> + # def find_subtree(&b) - if r = find_subtree_rec(@sent, &b) - Sentence.new(r) - else - nil - end + find_subtree_rec(@sent, &b) end + # :stopdoc: def find_subtree_rec(obj, &b) if obj.respond_to? :to_ary - if b.call obj - return obj + s = Sentence.new(obj) + if b.call s + return s else obj.each {|e| r = find_subtree_rec(e, &b) @@ -58,15 +238,33 @@ class Sentence end nil end + # :startdoc: + # returns a new sentence object which expands according to the condition + # given by the block. + # + # The block is invoked for each subsentence. + # The subsentences which the block returns true are + # expanded into parent. + # + # s = Sentence.new([%w[2 * 7], "+", %w[3 * 5]]) + # #=> #<Sentence: ("2" "*" "7") "+" ("3" "*" "5")> + # + # s.expand { true } + # #=> #<Sentence: "2" "*" "7" "+" "3" "*" "5"> + # + # s.expand {|s| s[0] == "3" } + # #=> #<Sentence: (("2" "*" "7") "+" "3" "*" "5")> + # def expand(&b) Sentence.new(expand_rec(@sent, &b)) end + # :stopdoc: def expand_rec(obj, r=[], &b) - #puts "#{obj.inspect}\t\t#{r.inspect}" if obj.respond_to? :to_ary - if b.call obj + s = Sentence.new(obj) + if b.call s obj.each {|o| expand_rec(o, r, &b) } @@ -82,13 +280,80 @@ class Sentence end r end + # :startdoc: + # Sentence.each generates sentences + # by deriving the start symbol _sym_ using _syntax_. + # The derivation is restricted by an positive integer _limit_ to + # avoid infinite generation. + # + # Sentence.each yields the block with a generated sentence. + # + # Sentence.each({ + # :exp => [["n"], + # [:exp, "+", :exp], + # [:exp, "*", :exp]] + # }, :exp, 1) {|sent| p sent } + # #=> + # #<Sentence: "n"> + # #<Sentence: ("n") "+" ("n")> + # #<Sentence: ("n") "*" ("n")> + # + # Sentence.each({ + # :exp => [["n"], + # [:exp, "+", :exp], + # [:exp, "*", :exp]] + # }, :exp, 2) {|sent| p sent } + # #=> + # #<Sentence: "n"> + # #<Sentence: ("n") "+" ("n")> + # #<Sentence: ("n") "+" (("n") "+" ("n"))> + # #<Sentence: ("n") "+" (("n") "*" ("n"))> + # #<Sentence: (("n") "+" ("n")) "+" ("n")> + # #<Sentence: (("n") "*" ("n")) "+" ("n")> + # #<Sentence: ("n") "*" ("n")> + # #<Sentence: ("n") "*" (("n") "+" ("n"))> + # #<Sentence: ("n") "*" (("n") "*" ("n"))> + # #<Sentence: (("n") "+" ("n")) "*" ("n")> + # #<Sentence: (("n") "*" ("n")) "*" ("n")> + # def Sentence.each(syntax, sym, limit) Gen.new(syntax).each_tree(sym, limit) {|tree| yield Sentence.new(tree) } end + # Sentence.expand_syntax returns an expanded syntax: + # * no underivable rule + # * no rule which derives only to empty sequence indirectly + # * no rule which is derivable to empty and non-empty + # * no channel rule + # + # Note that the rules which can derive empty and non-empty + # sequences are modified to derive only non-empty sequences. + # + # Sentence.expand_syntax({ + # :underivable => [[:underivable]], + # :just_empty1 => [[]], + # :just_empty2 => [[:just_empty1, :just_empty1]], + # :empty_or_not => [[], ["foo"]], + # :empty_or_not_2 => [[:empty_or_not, :empty_or_not]], + # :data => [["a", "b"], ["c", "d"]], + # :channel => [[:data]], + # }) + # #=> + # {:channel=>[["a", "b"], ["c", "d"]], + # :data=>[["a", "b"], ["c", "d"]], + # :empty_or_not=>[["foo"]], + # :empty_or_not_2=>[[], ["foo"], ["foo", "foo"]], + # :just_empty1=>[], + # :just_empty2=>[]} + # + def Sentence.expand_syntax(syntax) + Sentence::Gen.expand_syntax(syntax) + end + + # :stopdoc: class Gen def Gen.each_tree(syntax, sym, limit, &b) Gen.new(syntax).each_tree(sym, limit, &b) @@ -333,6 +598,7 @@ class Sentence nil end end + # :startdoc: end diff --git a/test/ruby/test_assignment.rb b/test/ruby/test_assignment.rb index 3a1e014714..c6b42d1b36 100644 --- a/test/ruby/test_assignment.rb +++ b/test/ruby/test_assignment.rb @@ -658,7 +658,7 @@ class TestAssignmentGen < Test::Unit::TestCase end def test_assignment - syntax = Sentence::Gen.expand_syntax(Syntax) + syntax = Sentence.expand_syntax(Syntax) Sentence.each(syntax, :xassign, 3) {|assign| assign, vars = rename_var(assign) sent = assign.to_s diff --git a/test/ruby/test_yield.rb b/test/ruby/test_yield.rb index 8bc4972bd3..3ae6881b87 100644 --- a/test/ruby/test_yield.rb +++ b/test/ruby/test_yield.rb @@ -322,14 +322,14 @@ class TestRubyYieldGen < Test::Unit::TestCase end def test_yield - syntax = Sentence::Gen.expand_syntax(Syntax) + syntax = Sentence.expand_syntax(Syntax) Sentence.each(syntax, :test_proc, 4) {|t| check_nofork(t) } end def test_yield_lambda - syntax = Sentence::Gen.expand_syntax(Syntax) + syntax = Sentence.expand_syntax(Syntax) Sentence.each(syntax, :test_lambda, 4) {|t| check_nofork(t, true) } |