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
|
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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue