madderlib 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +3 -0
- data/LICENSE +22 -0
- data/README.rdoc +173 -0
- data/Rakefile +134 -0
- data/lib/madderlib.rb +27 -0
- data/lib/madderlib/builder.rb +659 -0
- data/lib/madderlib/conditional/allowed.rb +144 -0
- data/lib/madderlib/conditional/helper.rb +135 -0
- data/lib/madderlib/conditional/likely.rb +162 -0
- data/lib/madderlib/conditional/recur.rb +103 -0
- data/lib/madderlib/conditional/registry.rb +56 -0
- data/lib/madderlib/conditional/repeat.rb +130 -0
- data/lib/madderlib/context.rb +140 -0
- data/lib/madderlib/core.rb +217 -0
- data/lib/madderlib/extensions.rb +40 -0
- data/lib/madderlib/instruction.rb +171 -0
- data/lib/madderlib/phrase.rb +284 -0
- data/lib/madderlib/sequencer.rb +337 -0
- data/madderlib.gemspec +72 -0
- data/spec/benchmark_spec.rb +69 -0
- data/spec/builder_spec.rb +321 -0
- data/spec/builder_to_other_spec.rb +47 -0
- data/spec/builder_to_sequencer_spec.rb +388 -0
- data/spec/conditional_allowed_spec.rb +130 -0
- data/spec/conditional_helper_spec.rb +131 -0
- data/spec/conditional_likely_spec.rb +138 -0
- data/spec/conditional_recur_spec.rb +102 -0
- data/spec/conditional_registry_spec.rb +94 -0
- data/spec/conditional_repeat_spec.rb +88 -0
- data/spec/doc_spec.rb +550 -0
- data/spec/error_spec.rb +33 -0
- data/spec/examples_spec.rb +151 -0
- data/spec/extensions_spec.rb +47 -0
- data/spec/grammar_spec.rb +101 -0
- data/spec/instruction_spec.rb +133 -0
- data/spec/kernel_spec.rb +58 -0
- data/spec/phrase_spec.rb +7 -0
- data/spec/sequencer_spec.rb +317 -0
- data/spec/spec_helper.rb +54 -0
- metadata +98 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
describe MadderLib::Conditional::Allowed do
|
6
|
+
|
7
|
+
it "supports single assumption" do
|
8
|
+
one, two = nil, nil
|
9
|
+
|
10
|
+
builder = madderlib do
|
11
|
+
say(:one).assuming {|test| one }
|
12
|
+
say(:two).presuming {|test| two }
|
13
|
+
say(:three).if { false }
|
14
|
+
end
|
15
|
+
|
16
|
+
[nil, true].each do |t|
|
17
|
+
two = t
|
18
|
+
[false, true].each do |o|
|
19
|
+
one = o
|
20
|
+
|
21
|
+
words = []
|
22
|
+
words << 'one' if one
|
23
|
+
words << 'two' if two
|
24
|
+
|
25
|
+
builder.words.should eql(words)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "supports multiple assumptions" do
|
31
|
+
one, two = nil, nil
|
32
|
+
|
33
|
+
builder = madderlib do
|
34
|
+
say(:something).if {|test| one }.assuming {|test| two }
|
35
|
+
end
|
36
|
+
|
37
|
+
[nil, true].each do |t|
|
38
|
+
two = t
|
39
|
+
[false, true].each do |o|
|
40
|
+
one = o
|
41
|
+
|
42
|
+
words = []
|
43
|
+
words << 'something' if one && two
|
44
|
+
|
45
|
+
builder.words.should eql(words)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "supports id-based assumption" do
|
51
|
+
# will say first = yes
|
52
|
+
builder = madderlib do
|
53
|
+
say(:yes).assuming(:spoken)
|
54
|
+
first(:spoken).say(:said)
|
55
|
+
end
|
56
|
+
|
57
|
+
builder.words.should eql(%w{ said yes })
|
58
|
+
|
59
|
+
# will say last = no
|
60
|
+
builder = madderlib do
|
61
|
+
say(:yes).if(:spoken)
|
62
|
+
last(:spoken).say(:said)
|
63
|
+
end
|
64
|
+
|
65
|
+
builder.words.should eql(%w{ said })
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
it "supports single forbiddance" do
|
71
|
+
one, two = nil, nil
|
72
|
+
|
73
|
+
builder = madderlib do
|
74
|
+
say(:one).forbidding {|test| one }
|
75
|
+
say(:two).forbidding {|test| two }
|
76
|
+
say(:three).unless { true }
|
77
|
+
end
|
78
|
+
|
79
|
+
[nil, true].each do |t|
|
80
|
+
two = t
|
81
|
+
[false, true].each do |o|
|
82
|
+
one = o
|
83
|
+
|
84
|
+
words = []
|
85
|
+
words << 'one' unless one
|
86
|
+
words << 'two' unless two
|
87
|
+
|
88
|
+
builder.words.should eql(words)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "supports multiple forbiddance" do
|
94
|
+
one, two = nil, nil
|
95
|
+
|
96
|
+
builder = madderlib do
|
97
|
+
say(:something).unless {|test| one }.forbidding {|test| two }
|
98
|
+
end
|
99
|
+
|
100
|
+
[nil, true].each do |t|
|
101
|
+
two = t
|
102
|
+
[false, true].each do |o|
|
103
|
+
one = o
|
104
|
+
|
105
|
+
words = []
|
106
|
+
words << 'something' unless one || two
|
107
|
+
|
108
|
+
builder.words.should eql(words)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it "supports id-based forbiddance" do
|
114
|
+
# will say first = no
|
115
|
+
builder = madderlib do
|
116
|
+
say(:yes).forbidding(:spoken)
|
117
|
+
first(:spoken).say(:said)
|
118
|
+
end
|
119
|
+
|
120
|
+
builder.words.should eql(%w{ said })
|
121
|
+
|
122
|
+
# will say last = yes
|
123
|
+
builder = madderlib do
|
124
|
+
say(:yes).unless(:spoken)
|
125
|
+
last(:spoken).say(:said)
|
126
|
+
end
|
127
|
+
|
128
|
+
builder.words.should eql(%w{ yes said })
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
describe MadderLib::Conditional::Helper do
|
6
|
+
|
7
|
+
it "a TestBlock fails with invalid arguments" do
|
8
|
+
# arg or block, not both
|
9
|
+
lambda {
|
10
|
+
MadderLib::Conditional::Helper::TestBlock.new(1) {|count| count < 1 }
|
11
|
+
}.should raise_error MadderLib::Error
|
12
|
+
|
13
|
+
# unsupported
|
14
|
+
lambda {
|
15
|
+
MadderLib::Conditional::Helper::TestBlock.new(:symbol)
|
16
|
+
}.should raise_error MadderLib::Error
|
17
|
+
end
|
18
|
+
|
19
|
+
it "a TestBlock with a block" do
|
20
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new {|count| count < 1 }
|
21
|
+
|
22
|
+
built = tester.block
|
23
|
+
|
24
|
+
built.call(0).should be_true
|
25
|
+
|
26
|
+
tester.invoke(0, :ignore).should be_true
|
27
|
+
tester.invoke(1, :ignore).should_not be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "a TestBlock with a fixed limit" do
|
31
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new(2)
|
32
|
+
|
33
|
+
built = tester.block
|
34
|
+
|
35
|
+
built.call(1).should be_true
|
36
|
+
built.call(2).should_not be_true
|
37
|
+
|
38
|
+
tester.invoke(1, :ignore).should be_true
|
39
|
+
tester.invoke(2, :ignore).should_not be_true
|
40
|
+
end
|
41
|
+
|
42
|
+
it "a TestBlock with a Range" do
|
43
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new(2, 4)
|
44
|
+
|
45
|
+
pound_on do
|
46
|
+
built = tester.block
|
47
|
+
|
48
|
+
built.call(1).should be_true
|
49
|
+
built.call(4).should_not be_true
|
50
|
+
end
|
51
|
+
|
52
|
+
tester.invoke(1, :ignore).should be_true
|
53
|
+
tester.invoke(4, :ignore).should_not be_true
|
54
|
+
|
55
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new(Range.new(3, 5))
|
56
|
+
|
57
|
+
pound_on do
|
58
|
+
built = tester.block
|
59
|
+
|
60
|
+
built.call(2).should be_true
|
61
|
+
built.call(5).should_not be_true
|
62
|
+
end
|
63
|
+
|
64
|
+
tester.invoke(2, :ignore).should be_true
|
65
|
+
tester.invoke(5, :ignore).should_not be_true
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
it "a TestBlock in minutes" do
|
71
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new(2, :minute)
|
72
|
+
|
73
|
+
pound_on do
|
74
|
+
built = tester.block
|
75
|
+
|
76
|
+
# 0, 1 ...
|
77
|
+
built.call(1).should be_true
|
78
|
+
built.call(2).should_not be_true
|
79
|
+
end
|
80
|
+
|
81
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new(2, 4, :minute)
|
82
|
+
|
83
|
+
pound_on do
|
84
|
+
built = tester.block
|
85
|
+
|
86
|
+
# 0, 1 ...
|
87
|
+
built.call(1).should be_true
|
88
|
+
built.call(4).should_not be_true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
it "can convert a TestBlock criterion into an integer" do
|
95
|
+
context = MadderLib::Context.new
|
96
|
+
|
97
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new(1)
|
98
|
+
tester.to_i(context).should eql(1)
|
99
|
+
|
100
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new(1, 2)
|
101
|
+
tester.to_i(context).should eql(2)
|
102
|
+
|
103
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new(Range.new(3, 4))
|
104
|
+
tester.to_i(context).should eql(4)
|
105
|
+
|
106
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new { 2 + 2 }
|
107
|
+
tester.to_i(context).should eql(4)
|
108
|
+
|
109
|
+
context.data[:value] = 5
|
110
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new {|context| context.data[:value] }
|
111
|
+
tester.to_i(context).should eql(5)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "invokes the contained block, or one explicitly provided" do
|
115
|
+
# arity < 1
|
116
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new { 2 }
|
117
|
+
tester.invoke.should eql(2)
|
118
|
+
tester.invoke { 0 }.should eql(0)
|
119
|
+
|
120
|
+
# arity = 1
|
121
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new {|value| value + 1 }
|
122
|
+
tester.invoke(1).should eql(2)
|
123
|
+
tester.invoke(1) {|value| value - 1 }.should eql(0)
|
124
|
+
|
125
|
+
# arity = 2, discard ignored
|
126
|
+
tester = MadderLib::Conditional::Helper::TestBlock.new {|a, b| a + b }
|
127
|
+
tester.invoke(1, 1, :ignored).should eql(2)
|
128
|
+
tester.invoke(1, 1, :ignored) {|a, b| a - b }.should eql(0)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
describe MadderLib::Conditional::Likely do
|
6
|
+
def distribution(words, map=nil)
|
7
|
+
map ||= {}
|
8
|
+
words.each do |word|
|
9
|
+
map[word] = (map[word] || 0) + 1
|
10
|
+
end
|
11
|
+
|
12
|
+
return map
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
it "by default has a 50/50 split" do
|
18
|
+
builder = madderlib do
|
19
|
+
say 'buffalo'
|
20
|
+
it.alternately.says 'fish'
|
21
|
+
end
|
22
|
+
|
23
|
+
refs = %w{ buffalo fish }
|
24
|
+
map = nil
|
25
|
+
pound_on do
|
26
|
+
map = distribution(builder.words, map)
|
27
|
+
end
|
28
|
+
|
29
|
+
# now that'we re done
|
30
|
+
# should have hit each once
|
31
|
+
# and should have a reasonable distribution (though who can tell)
|
32
|
+
map.should have(refs.size).items
|
33
|
+
map.keys.each {|key| refs.include?(key).should be_true }
|
34
|
+
|
35
|
+
map['buffalo'].should be_close(50, 20)
|
36
|
+
map['fish'].should be_close(50, 20)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "supports nothing" do
|
40
|
+
builder = madderlib do
|
41
|
+
say 'something nice'
|
42
|
+
alternately.nothing
|
43
|
+
end
|
44
|
+
|
45
|
+
map = nil
|
46
|
+
pound_on(100) do
|
47
|
+
map = distribution(builder.words, map)
|
48
|
+
end
|
49
|
+
|
50
|
+
# now that'we re done
|
51
|
+
# should have hit each once
|
52
|
+
# and should have a reasonable distribution (though who can tell)
|
53
|
+
map.should have(1).items
|
54
|
+
map['something nice'].should_not be_nil
|
55
|
+
|
56
|
+
map['something nice'].should be_close(50, 20)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
it "getting a 2/1 split" do
|
62
|
+
# testing out various alias formats, etc
|
63
|
+
builder = madderlib :split_2_1 do
|
64
|
+
say 'buffalo'
|
65
|
+
it.alternately.says('fish').likely(2)
|
66
|
+
end
|
67
|
+
|
68
|
+
refs = %w{ buffalo fish }
|
69
|
+
map = nil
|
70
|
+
pound_on do
|
71
|
+
words = builder.words
|
72
|
+
map = distribution(words, map)
|
73
|
+
end
|
74
|
+
|
75
|
+
# now that'we re done
|
76
|
+
# should have hit each once
|
77
|
+
# and should have a reasonable distribution (though who can tell)
|
78
|
+
map.should have(refs.size).items
|
79
|
+
map.keys.each {|key| refs.include?(key).should be_true }
|
80
|
+
|
81
|
+
map['buffalo'].should be_close(33, 20)
|
82
|
+
map['fish'].should be_close(66, 20)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "getting a 3/1 split" do
|
86
|
+
# testing out various alias formats, etcnext
|
87
|
+
builder = madderlib :split_3_1 do
|
88
|
+
say 'buffalo'
|
89
|
+
it.alternately.says('fish').weighing { 2 + 1 }
|
90
|
+
end
|
91
|
+
|
92
|
+
refs = %w{ buffalo fish }
|
93
|
+
map = nil
|
94
|
+
pound_on do
|
95
|
+
words = builder.words
|
96
|
+
map = distribution(words, map)
|
97
|
+
end
|
98
|
+
|
99
|
+
# now that'we re done
|
100
|
+
# should have hit each once
|
101
|
+
# and should have a reasonable distribution (though who can tell)
|
102
|
+
map.should have(refs.size).items
|
103
|
+
map.keys.each {|key| refs.include?(key).should be_true }
|
104
|
+
|
105
|
+
map['buffalo'].should be_close(25, 20)
|
106
|
+
map['fish'].should be_close(75, 20)
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
it "getting a 3/2/1 split" do
|
112
|
+
# testing out transferrence from 'or' operation to likelihood
|
113
|
+
builder = madderlib :split_3_2_1 do
|
114
|
+
say 'faith'
|
115
|
+
alternately(2).says('hope')
|
116
|
+
# range is supported, but not recommended
|
117
|
+
it.or(0..3).says('charity')
|
118
|
+
end
|
119
|
+
|
120
|
+
refs = %w{ faith hope charity }
|
121
|
+
map = nil
|
122
|
+
pound_on 150 do
|
123
|
+
words = builder.words
|
124
|
+
map = distribution(words, map)
|
125
|
+
end
|
126
|
+
|
127
|
+
# now that'we re done
|
128
|
+
# should have hit each once
|
129
|
+
# and should have a reasonable distribution (though who can tell)
|
130
|
+
map.should have(refs.size).items
|
131
|
+
map.keys.each {|key| refs.include?(key).should be_true }
|
132
|
+
|
133
|
+
map['faith'].should be_close(25, 20)
|
134
|
+
map['hope'].should be_close(50, 20)
|
135
|
+
map['charity'].should be_close(75, 20)
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
describe MadderLib::Conditional::Recur do
|
6
|
+
|
7
|
+
it "only occurs once if not told otherwise" do
|
8
|
+
builder = madderlib do
|
9
|
+
# give it boundaries
|
10
|
+
say(:start)
|
11
|
+
anytime.say(:any)
|
12
|
+
say(:end)
|
13
|
+
end
|
14
|
+
|
15
|
+
builder.phrases.each do |phrase|
|
16
|
+
phrase.recurs?.should be_false if phrase.respond_to?(:recurs?)
|
17
|
+
end
|
18
|
+
|
19
|
+
builder.words.should eql(%w{ start any end })
|
20
|
+
end
|
21
|
+
|
22
|
+
it "supports simple recurrence" do
|
23
|
+
builder = madderlib do
|
24
|
+
# give it boundaries
|
25
|
+
say(:start)
|
26
|
+
anytime.recurring(2).say(:any)
|
27
|
+
say(:end)
|
28
|
+
end
|
29
|
+
|
30
|
+
builder.phrases.each do |phrase|
|
31
|
+
phrase.recurs?.should be_true if phrase.respond_to?(:recurs?)
|
32
|
+
end
|
33
|
+
|
34
|
+
builder.words.should eql(%w{ start any any end })
|
35
|
+
end
|
36
|
+
|
37
|
+
it "supports a 0-recurrence, which blocks the phrase" do
|
38
|
+
builder = madderlib do
|
39
|
+
say(:start)
|
40
|
+
anytime.recur(0).say(:never)
|
41
|
+
say(:end)
|
42
|
+
end
|
43
|
+
|
44
|
+
builder.words.should eql(%w{ start end })
|
45
|
+
end
|
46
|
+
|
47
|
+
it "supports Ranged recurrence" do
|
48
|
+
builder = madderlib do
|
49
|
+
# give it boundaries
|
50
|
+
say(:start)
|
51
|
+
anytime.recurring(Range.new(3, 5)).say(:few)
|
52
|
+
anytime.recurring(3, 5).say(:couple)
|
53
|
+
say(:end)
|
54
|
+
end
|
55
|
+
|
56
|
+
pound_on do
|
57
|
+
words = builder.words
|
58
|
+
|
59
|
+
words.shift.should eql('start')
|
60
|
+
words.pop.should eql('end')
|
61
|
+
|
62
|
+
count = 0
|
63
|
+
words.each {|word| count += 1 if word == 'few' }
|
64
|
+
count.should be_close(4, 1.1)
|
65
|
+
|
66
|
+
count = 0
|
67
|
+
words.each {|word| count += 1 if word == 'couple' }
|
68
|
+
count.should be_close(4, 1.1)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "supports arbitrary recurrence" do
|
73
|
+
spices = [:parsley, :sage, :rosemary, :thyme]
|
74
|
+
shoulds = spices.clone
|
75
|
+
|
76
|
+
builder = madderlib :repeat_exhausting do
|
77
|
+
say(:start)
|
78
|
+
# if these don't exist, positioning isn't random
|
79
|
+
# it's just a visual confirmation from initial testing
|
80
|
+
say(:salt)
|
81
|
+
say(:pepper)
|
82
|
+
say(:flour)
|
83
|
+
say(:end)
|
84
|
+
|
85
|
+
anytime.recur { ! spices.empty? }.say { spices.shift }
|
86
|
+
end
|
87
|
+
|
88
|
+
words = builder.words
|
89
|
+
words.shift.should eql('start')
|
90
|
+
words.pop.should eql('end')
|
91
|
+
|
92
|
+
# and all of the others should be present
|
93
|
+
shoulds << :salt << :pepper << :flour
|
94
|
+
shoulds.each do |spice|
|
95
|
+
words.index(spice.to_s).should_not be_nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# the next pass should be exhausted
|
99
|
+
builder.words.should eql(%w{ start salt pepper flour end })
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|