pure 0.1.0 → 0.2.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/CHANGES.rdoc +7 -0
- data/MANIFEST +44 -20
- data/README.rdoc +553 -16
- data/Rakefile +25 -2
- data/devel/jumpstart.rb +606 -253
- data/install.rb +1 -2
- data/lib/pure.rb +38 -16
- data/lib/pure/bundled_parsers.rb +4 -0
- data/lib/pure/bundled_plugin.rb +49 -0
- data/lib/pure/compiler/ruby_parser.rb +63 -0
- data/lib/pure/delegate.rb +16 -0
- data/lib/pure/driver.rb +33 -0
- data/lib/pure/dsl.rb +2 -0
- data/lib/pure/dsl_definition.rb +11 -0
- data/lib/pure/error.rb +89 -0
- data/lib/pure/extracted_functions.rb +11 -0
- data/lib/pure/extractor.rb +59 -0
- data/lib/pure/names.rb +9 -0
- data/lib/pure/native_worker.rb +27 -0
- data/lib/pure/parser/impl/base_parser.rb +21 -0
- data/lib/pure/parser/impl/internal.rb +31 -0
- data/lib/pure/parser/impl/ripper.rb +96 -0
- data/lib/pure/parser/impl/ruby_parser.rb +77 -0
- data/lib/pure/parser/internal.rb +4 -0
- data/lib/pure/parser/ripper.rb +2 -0
- data/lib/pure/parser/ruby_parser.rb +2 -0
- data/lib/pure/pure.rb +32 -0
- data/lib/pure/pure_module.rb +141 -0
- data/lib/pure/util.rb +15 -0
- data/lib/pure/version.rb +4 -0
- data/spec/compiler_ruby_parser_spec.rb +79 -0
- data/spec/compute_overrides_spec.rb +99 -0
- data/spec/compute_spec.rb +86 -0
- data/spec/compute_thread_spec.rb +29 -0
- data/spec/compute_timed_spec.rb +40 -0
- data/spec/delegate_spec.rb +141 -0
- data/spec/fstat_example.rb +26 -0
- data/spec/parser_sexp_spec.rb +100 -0
- data/spec/parser_spec.rb +18 -31
- data/spec/pure_combine_spec.rb +77 -0
- data/spec/pure_def_spec.rb +186 -0
- data/spec/pure_define_method_spec.rb +24 -0
- data/spec/pure_eval_spec.rb +18 -0
- data/spec/pure_fun_spec.rb +243 -0
- data/spec/pure_nested_spec.rb +35 -0
- data/spec/pure_parser_spec.rb +50 -0
- data/spec/pure_spec.rb +81 -0
- data/spec/pure_spec_base.rb +106 -0
- data/spec/pure_splat_spec.rb +18 -0
- data/spec/pure_two_defs_spec.rb +20 -0
- data/spec/pure_worker_spec.rb +33 -0
- data/spec/readme_spec.rb +36 -32
- data/spec/splat_spec.rb +12 -11
- data/spec/worker_spec.rb +89 -0
- metadata +157 -41
- data/devel/jumpstart/lazy_attribute.rb +0 -38
- data/devel/jumpstart/ruby.rb +0 -44
- data/devel/jumpstart/simple_installer.rb +0 -85
- data/lib/pure/pure_private/creator.rb +0 -27
- data/lib/pure/pure_private/driver.rb +0 -48
- data/lib/pure/pure_private/error.rb +0 -32
- data/lib/pure/pure_private/extractor.rb +0 -79
- data/lib/pure/pure_private/extractor_ripper.rb +0 -95
- data/lib/pure/pure_private/extractor_ruby_parser.rb +0 -47
- data/lib/pure/pure_private/function_database.rb +0 -10
- data/lib/pure/pure_private/singleton_features.rb +0 -67
- data/lib/pure/pure_private/util.rb +0 -23
- data/spec/basic_spec.rb +0 -38
- data/spec/combine_spec.rb +0 -62
- data/spec/common.rb +0 -44
- data/spec/error_spec.rb +0 -146
- data/spec/fun_spec.rb +0 -122
- data/spec/lazy_spec.rb +0 -22
- data/spec/subseqent_spec.rb +0 -42
- data/spec/timed_spec.rb +0 -30
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/pure_spec_base'
|
2
|
+
|
3
|
+
describe "compute" do
|
4
|
+
describe "overrides" do
|
5
|
+
it "should replace missing functions" do
|
6
|
+
pure do
|
7
|
+
def add(x, y)
|
8
|
+
x + y
|
9
|
+
end
|
10
|
+
end.compute(4, :x => 33, :y => 44).add.should == 77
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should fail if underspecified" do
|
14
|
+
(1..4).each { |n|
|
15
|
+
lambda {
|
16
|
+
pure do
|
17
|
+
def add(x, y)
|
18
|
+
x + y
|
19
|
+
end
|
20
|
+
end.compute(n, :y => 44).add
|
21
|
+
}.should raise_error(Pure::NoFunctionError, "no function named `x'")
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should override no-argument functions" do
|
26
|
+
(1..4).each { |n|
|
27
|
+
pure do
|
28
|
+
def add(x, y)
|
29
|
+
x + y
|
30
|
+
end
|
31
|
+
def x
|
32
|
+
33
|
33
|
+
end
|
34
|
+
def y
|
35
|
+
44
|
36
|
+
end
|
37
|
+
end.compute(n, :x => 55).add.should == 99
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should override multi-argument functions" do
|
42
|
+
(1..4).each { |n|
|
43
|
+
pure do
|
44
|
+
def add(x, y)
|
45
|
+
x + y
|
46
|
+
end
|
47
|
+
def x
|
48
|
+
33
|
49
|
+
end
|
50
|
+
def y
|
51
|
+
44
|
52
|
+
end
|
53
|
+
end.compute(n, :add => 11).add.should == 11
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should override a root function" do
|
58
|
+
(1..4).each { |n|
|
59
|
+
greet = pure do
|
60
|
+
def hello(name)
|
61
|
+
"Hello, #{name}."
|
62
|
+
end
|
63
|
+
|
64
|
+
def name
|
65
|
+
"Bob"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
greet.compute(n).hello.should eql("Hello, Bob.")
|
69
|
+
greet.compute(n, :name => "Ralph").hello.should eql("Hello, Ralph.")
|
70
|
+
greet.compute(n, :hello => "Goodbye.").hello.should eql("Goodbye.")
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should prevent the overridden function from being called" do
|
75
|
+
count = 0
|
76
|
+
pure do
|
77
|
+
def f x
|
78
|
+
x + 44
|
79
|
+
end
|
80
|
+
|
81
|
+
fun :x do
|
82
|
+
count += 1
|
83
|
+
33
|
84
|
+
end
|
85
|
+
end.compute(:x => 11).f.should eql(55)
|
86
|
+
count.should == 0
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should convert non-symbol keys to symbols" do
|
90
|
+
(1..4).each { |n|
|
91
|
+
pure do
|
92
|
+
def add(x, y)
|
93
|
+
x + y
|
94
|
+
end
|
95
|
+
end.compute(n, "x" => 55, "y" => 44).add.should == 99
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/pure_spec_base'
|
2
|
+
|
3
|
+
describe "compute" do
|
4
|
+
it "should accept no arguments" do
|
5
|
+
pure do
|
6
|
+
def f
|
7
|
+
33
|
8
|
+
end
|
9
|
+
end.compute.f.should == 33
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should accept (overrides) argument" do
|
13
|
+
pure do
|
14
|
+
def f
|
15
|
+
33
|
16
|
+
end
|
17
|
+
end.compute(:f => 44).f.should == 44
|
18
|
+
end
|
19
|
+
it "should accept (num_parallel) argument" do
|
20
|
+
(1..8).each { |n|
|
21
|
+
pure do
|
22
|
+
def f
|
23
|
+
33
|
24
|
+
end
|
25
|
+
end.compute(n).f.should == 33
|
26
|
+
}
|
27
|
+
end
|
28
|
+
it "should accept (worker) argument" do
|
29
|
+
pure do
|
30
|
+
def f
|
31
|
+
33
|
32
|
+
end
|
33
|
+
end.compute(Pure::NativeWorker).f.should == 33
|
34
|
+
end
|
35
|
+
it "should accept (num_parallel, overrides) arguments" do
|
36
|
+
(1..8).each { |n|
|
37
|
+
pure do
|
38
|
+
def f
|
39
|
+
33
|
40
|
+
end
|
41
|
+
end.compute(n, :f => 99).f.should == 99
|
42
|
+
}
|
43
|
+
end
|
44
|
+
it "should accept (worker, overrides) arguments" do
|
45
|
+
pure do
|
46
|
+
def f
|
47
|
+
33
|
48
|
+
end
|
49
|
+
end.compute(Pure::NativeWorker, :f => 99).f.should == 99
|
50
|
+
end
|
51
|
+
it "should raise error for > 2 arguments" do
|
52
|
+
lambda {
|
53
|
+
pure do
|
54
|
+
def f
|
55
|
+
33
|
56
|
+
end
|
57
|
+
end.compute(:a, :b, :c).f.should == 99
|
58
|
+
}.should raise_error(ArgumentError)
|
59
|
+
end
|
60
|
+
it "should raise #{Pure::NoFunctionError} for undefined function" do
|
61
|
+
(1..8).each { |n|
|
62
|
+
lambda {
|
63
|
+
result = pure do
|
64
|
+
def f(x)
|
65
|
+
x + 33
|
66
|
+
end
|
67
|
+
end.compute(n).f
|
68
|
+
}.should raise_error(Pure::NoFunctionError, "no function named `x'")
|
69
|
+
}
|
70
|
+
end
|
71
|
+
it "should be independent of other compute calls" do
|
72
|
+
mod_f1 = pure do
|
73
|
+
def f
|
74
|
+
1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
mod_f2 = pure do
|
78
|
+
def f
|
79
|
+
2
|
80
|
+
end
|
81
|
+
end
|
82
|
+
result_f1 = mod_f1.compute(3)
|
83
|
+
result_f2 = mod_f2.compute(4)
|
84
|
+
result_f1.f.should == 1
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/pure_spec_base'
|
2
|
+
|
3
|
+
describe "compute" do
|
4
|
+
max_threads = 50
|
5
|
+
describe "with number of threads 1..#{max_threads}" do
|
6
|
+
it "should succeed" do
|
7
|
+
mod = pure do
|
8
|
+
def area(width, height)
|
9
|
+
width*height
|
10
|
+
end
|
11
|
+
|
12
|
+
def width(border)
|
13
|
+
20 + border
|
14
|
+
end
|
15
|
+
|
16
|
+
def height(border)
|
17
|
+
30 + border
|
18
|
+
end
|
19
|
+
|
20
|
+
def border
|
21
|
+
5
|
22
|
+
end
|
23
|
+
end
|
24
|
+
(1..max_threads).each { |n|
|
25
|
+
mod.compute(n) { |s| s.area }.should == (20 + 5)*(30 + 5)
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/pure_spec_base'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
# factor out due to parsers changing
|
5
|
+
def create_timed_benchmark
|
6
|
+
mod = pure do
|
7
|
+
def root(a, b)
|
8
|
+
end
|
9
|
+
|
10
|
+
def a
|
11
|
+
sleep(0.25)
|
12
|
+
end
|
13
|
+
|
14
|
+
def b
|
15
|
+
sleep(0.25)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
lambda { |n|
|
19
|
+
mod.compute(n)[:root]
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rbconfig'
|
24
|
+
|
25
|
+
# uneven results in windows
|
26
|
+
unless Config::CONFIG["host"] =~ %r!(mswin|cygwin|mingw)!
|
27
|
+
describe "compute" do
|
28
|
+
describe "with timed example" do
|
29
|
+
it "should run with 1 thread" do
|
30
|
+
compute = create_timed_benchmark
|
31
|
+
Benchmark.measure { compute.call(1) }.real.should be_close(0.5, 0.1)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should be 2x faster with 2 threads" do
|
35
|
+
compute = create_timed_benchmark
|
36
|
+
Benchmark.measure { compute.call(2) }.real.should be_close(0.25, 0.05)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/pure_spec_base'
|
2
|
+
|
3
|
+
if RUBY_PLATFORM == "java"
|
4
|
+
# jruby needs this here; don't know why
|
5
|
+
require 'ruby2ruby'
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_geometry_example
|
9
|
+
pure do
|
10
|
+
def area(width, height)
|
11
|
+
width*height
|
12
|
+
end
|
13
|
+
|
14
|
+
def width(border)
|
15
|
+
7 + border
|
16
|
+
end
|
17
|
+
|
18
|
+
def height(border)
|
19
|
+
5 + border
|
20
|
+
end
|
21
|
+
end.compute(4, :border => 2)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe Pure::Delegate do
|
25
|
+
it "should hold results" do
|
26
|
+
geometry = create_geometry_example
|
27
|
+
geometry.area.should eql((7 + 2)*(5 + 2))
|
28
|
+
geometry.width.should eql(7 + 2)
|
29
|
+
geometry.height.should eql(5 + 2)
|
30
|
+
geometry.border.should eql(2)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should raise error for undefined attribute" do
|
34
|
+
lambda {
|
35
|
+
pure do
|
36
|
+
def f
|
37
|
+
33
|
38
|
+
end
|
39
|
+
end.compute.g
|
40
|
+
}.should raise_error(NoMethodError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should raise error when attribute is passed args" do
|
44
|
+
lambda {
|
45
|
+
pure do
|
46
|
+
def f
|
47
|
+
33
|
48
|
+
end
|
49
|
+
end.compute.f(44)
|
50
|
+
}.should raise_error(ArgumentError)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should be lazy" do
|
54
|
+
mutex = Mutex.new
|
55
|
+
counter = 0
|
56
|
+
incrementer = pure do
|
57
|
+
fun :increment do
|
58
|
+
mutex.synchronize {
|
59
|
+
counter += 1
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end.compute(Pure::NativeWorker)
|
63
|
+
counter.should eql(0)
|
64
|
+
3.times {
|
65
|
+
incrementer.increment.should eql(1)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "[] alternative to method call" do
|
70
|
+
it "should accept symbols" do
|
71
|
+
geometry = create_geometry_example
|
72
|
+
geometry[:area].should eql((7 + 2)*(5 + 2))
|
73
|
+
geometry[:width].should eql(7 + 2)
|
74
|
+
geometry[:height].should eql(5 + 2)
|
75
|
+
geometry[:border].should eql(2)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should accept strings" do
|
79
|
+
geometry = create_geometry_example
|
80
|
+
geometry["area"].should eql((7 + 2)*(5 + 2))
|
81
|
+
geometry["width"].should eql(7 + 2)
|
82
|
+
geometry["height"].should eql(5 + 2)
|
83
|
+
geometry["border"].should eql(2)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should raise error if given > 1 arg" do
|
87
|
+
geometry = create_geometry_example
|
88
|
+
lambda {
|
89
|
+
geometry[:area, 4]
|
90
|
+
}.should raise_error(ArgumentError)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should raise error when given other types" do
|
94
|
+
lambda {
|
95
|
+
pure do
|
96
|
+
def f
|
97
|
+
end
|
98
|
+
end.compute(33)[nil]
|
99
|
+
}.should raise_error(TypeError)
|
100
|
+
|
101
|
+
lambda {
|
102
|
+
pure do
|
103
|
+
def f
|
104
|
+
end
|
105
|
+
end.compute(33)[Object.new]
|
106
|
+
}.should raise_error(TypeError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should propagate exceptions from root function" do
|
110
|
+
mod = pure do
|
111
|
+
def f
|
112
|
+
raise "zz"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
result = mod.compute(4)
|
116
|
+
lambda {
|
117
|
+
result.f
|
118
|
+
}.should raise_error(RuntimeError, "zz")
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should propagate exceptions from child functions" do
|
122
|
+
mod = pure do
|
123
|
+
def f(x)
|
124
|
+
33
|
125
|
+
end
|
126
|
+
|
127
|
+
def x(y)
|
128
|
+
44
|
129
|
+
end
|
130
|
+
|
131
|
+
def y
|
132
|
+
raise "foo"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
result = mod.compute(4)
|
136
|
+
lambda {
|
137
|
+
result.f
|
138
|
+
}.should raise_error(RuntimeError, "foo")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/pure_spec_base'
|
2
|
+
|
3
|
+
describe "fstat example" do
|
4
|
+
it "should succeed" do
|
5
|
+
files = Dir["*"]
|
6
|
+
|
7
|
+
stats = pure do
|
8
|
+
files.each { |file|
|
9
|
+
fun file do
|
10
|
+
File.stat(fun_name.to_s)
|
11
|
+
end
|
12
|
+
}
|
13
|
+
|
14
|
+
fun :total_size => files do |*values|
|
15
|
+
values.inject(0) { |acc, stat| acc + stat.size }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
stats.compute { |result|
|
20
|
+
result["Rakefile"].size.should eql(File.stat("Rakefile").size)
|
21
|
+
total = files.inject(0) { |acc, file| acc + File.stat(file).size }
|
22
|
+
result.total_size.should == total
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/pure_spec_base'
|
2
|
+
|
3
|
+
def check_sexp(type, mod, name, expected)
|
4
|
+
entry = Pure::ExtractedFunctions[Pure.parser][mod][name]
|
5
|
+
if Pure.parser == (Pure::Parser::RubyParser rescue nil)
|
6
|
+
check_ruby_parser_sexp(entry, type, mod, expected)
|
7
|
+
elsif Pure.parser == (Pure::Parser::Ripper rescue nil)
|
8
|
+
check_ripper_sexp(entry, type, mod, expected)
|
9
|
+
elsif Pure.parser == (Pure::Parser::Internal rescue nil)
|
10
|
+
check_internal_sexp(entry, type, mod, expected)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_ruby_parser_sexp(entry, type, mod, expected)
|
15
|
+
entry[:code].should be_a(Sexp)
|
16
|
+
sexp = Pure::Parser::RubyParser::DupSexp.dup_sexp(entry[:code])
|
17
|
+
require 'ruby2ruby'
|
18
|
+
recovered = Ruby2Ruby.new.process(sexp).strip.gsub(%r!\s+!, " ")
|
19
|
+
recovered.should == expected
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_ripper_sexp(entry, type, mod, expected)
|
23
|
+
sexp = entry[:code]
|
24
|
+
sexp.should be_a(Array)
|
25
|
+
case entry[:origin]
|
26
|
+
when :def
|
27
|
+
sexp.first.should == :def
|
28
|
+
when :fun
|
29
|
+
sexp.first.should == :do_block
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_internal_sexp(entry, type, mod, expected)
|
34
|
+
entry[:code].should == nil
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "parser" do
|
38
|
+
describe "sexp" do
|
39
|
+
it "should match the source" do
|
40
|
+
def_f = pure do
|
41
|
+
def f(x, y)
|
42
|
+
x + y
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
fun_f = pure do
|
47
|
+
fun :f => [:x, :y] do |a, b|
|
48
|
+
a + b
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def_g = pure do
|
53
|
+
def g
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
fun_g = pure do
|
58
|
+
fun :g do
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def_h = pure do
|
63
|
+
def h(x)
|
64
|
+
x**2
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
fun_h = pure do
|
69
|
+
fun :h => :x do |a|
|
70
|
+
a**2
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
fun_i = pure do
|
75
|
+
fun :i => [:p, :q] do |*s|
|
76
|
+
s.size
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
fun_j = pure do
|
81
|
+
fun :j => [:p, :q] do |r, *s|
|
82
|
+
r + s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
[
|
87
|
+
[:def, def_f, :f, "def f(x, y) (x + y) end"],
|
88
|
+
[:fun, fun_f, :f, "fun(:f => ([:x, :y])) { |a, b| (a + b) }"],
|
89
|
+
[:def, def_g, :g, "def g # do nothing end"],
|
90
|
+
[:fun, fun_g, :g, "fun(:g) { }"],
|
91
|
+
[:def, def_h, :h, "def h(x) (x ** 2) end"],
|
92
|
+
[:fun, fun_h, :h, "fun(:h => :x) { |a| (a ** 2) }"],
|
93
|
+
[:fun, fun_i, :i, "fun(:i => ([:p, :q])) { |*s| s.size }"],
|
94
|
+
[:fun, fun_j, :j, "fun(:j => ([:p, :q])) { |r, *s| (r + s) }"],
|
95
|
+
].each { |type, mod, name, expected|
|
96
|
+
check_sexp(type, mod, name, expected)
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|