1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

sentence.rb documented.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@13108 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
akr 2007-08-18 17:04:06 +00:00
parent 7017b63b81
commit 9e5d0d861f
3 changed files with 281 additions and 15 deletions

View file

@ -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 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) def initialize(ary)
@sent = ary @sent = ary
end 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 def to_s
@sent.join('') @sent.join('')
end 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 def to_a
@sent @sent
end 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) 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 end
def inspect def inspect
"#<#{self.class}: #{@sent.inspect}>" "#<#{self.class}: #{inner_inspect(@sent, '')}>"
end 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) def subst(target, &b)
Sentence.new(subst_rec(@sent, target, &b)) Sentence.new(subst_rec(@sent, target, &b))
end end
# :stopdoc:
def subst_rec(obj, target, &b) def subst_rec(obj, target, &b)
if obj.respond_to? :to_ary if obj.respond_to? :to_ary
a = [] a = []
@ -36,19 +210,25 @@ class Sentence
obj obj
end end
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) def find_subtree(&b)
if r = find_subtree_rec(@sent, &b) find_subtree_rec(@sent, &b)
Sentence.new(r)
else
nil
end
end end
# :stopdoc:
def find_subtree_rec(obj, &b) def find_subtree_rec(obj, &b)
if obj.respond_to? :to_ary if obj.respond_to? :to_ary
if b.call obj s = Sentence.new(obj)
return obj if b.call s
return s
else else
obj.each {|e| obj.each {|e|
r = find_subtree_rec(e, &b) r = find_subtree_rec(e, &b)
@ -58,15 +238,33 @@ class Sentence
end end
nil nil
end 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) def expand(&b)
Sentence.new(expand_rec(@sent, &b)) Sentence.new(expand_rec(@sent, &b))
end end
# :stopdoc:
def expand_rec(obj, r=[], &b) def expand_rec(obj, r=[], &b)
#puts "#{obj.inspect}\t\t#{r.inspect}"
if obj.respond_to? :to_ary if obj.respond_to? :to_ary
if b.call obj s = Sentence.new(obj)
if b.call s
obj.each {|o| obj.each {|o|
expand_rec(o, r, &b) expand_rec(o, r, &b)
} }
@ -82,13 +280,80 @@ class Sentence
end end
r r
end 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) def Sentence.each(syntax, sym, limit)
Gen.new(syntax).each_tree(sym, limit) {|tree| Gen.new(syntax).each_tree(sym, limit) {|tree|
yield Sentence.new(tree) yield Sentence.new(tree)
} }
end 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 class Gen
def Gen.each_tree(syntax, sym, limit, &b) def Gen.each_tree(syntax, sym, limit, &b)
Gen.new(syntax).each_tree(sym, limit, &b) Gen.new(syntax).each_tree(sym, limit, &b)
@ -333,6 +598,7 @@ class Sentence
nil nil
end end
end end
# :startdoc:
end end

View file

@ -658,7 +658,7 @@ class TestAssignmentGen < Test::Unit::TestCase
end end
def test_assignment def test_assignment
syntax = Sentence::Gen.expand_syntax(Syntax) syntax = Sentence.expand_syntax(Syntax)
Sentence.each(syntax, :xassign, 3) {|assign| Sentence.each(syntax, :xassign, 3) {|assign|
assign, vars = rename_var(assign) assign, vars = rename_var(assign)
sent = assign.to_s sent = assign.to_s

View file

@ -322,14 +322,14 @@ class TestRubyYieldGen < Test::Unit::TestCase
end end
def test_yield def test_yield
syntax = Sentence::Gen.expand_syntax(Syntax) syntax = Sentence.expand_syntax(Syntax)
Sentence.each(syntax, :test_proc, 4) {|t| Sentence.each(syntax, :test_proc, 4) {|t|
check_nofork(t) check_nofork(t)
} }
end end
def test_yield_lambda def test_yield_lambda
syntax = Sentence::Gen.expand_syntax(Syntax) syntax = Sentence.expand_syntax(Syntax)
Sentence.each(syntax, :test_lambda, 4) {|t| Sentence.each(syntax, :test_lambda, 4) {|t|
check_nofork(t, true) check_nofork(t, true)
} }