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:
parent
7017b63b81
commit
9e5d0d861f
3 changed files with 281 additions and 15 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue