pure 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/CHANGES.rdoc +7 -0
- data/MANIFEST +30 -0
- data/README.rdoc +59 -0
- data/Rakefile +10 -0
- data/devel/jumpstart.rb +634 -0
- data/devel/jumpstart/lazy_attribute.rb +38 -0
- data/devel/jumpstart/ruby.rb +44 -0
- data/devel/jumpstart/simple_installer.rb +85 -0
- data/install.rb +3 -0
- data/lib/pure.rb +19 -0
- data/lib/pure/pure_private/creator.rb +27 -0
- data/lib/pure/pure_private/driver.rb +48 -0
- data/lib/pure/pure_private/error.rb +32 -0
- data/lib/pure/pure_private/extractor.rb +79 -0
- data/lib/pure/pure_private/extractor_ripper.rb +95 -0
- data/lib/pure/pure_private/extractor_ruby_parser.rb +47 -0
- data/lib/pure/pure_private/function_database.rb +10 -0
- data/lib/pure/pure_private/singleton_features.rb +67 -0
- data/lib/pure/pure_private/util.rb +23 -0
- data/spec/basic_spec.rb +38 -0
- data/spec/combine_spec.rb +62 -0
- data/spec/common.rb +44 -0
- data/spec/error_spec.rb +146 -0
- data/spec/fun_spec.rb +122 -0
- data/spec/lazy_spec.rb +22 -0
- data/spec/parser_spec.rb +36 -0
- data/spec/readme_spec.rb +35 -0
- data/spec/splat_spec.rb +16 -0
- data/spec/subseqent_spec.rb +42 -0
- data/spec/timed_spec.rb +30 -0
- metadata +135 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
require 'ruby_parser'
|
3
|
+
require 'sexp_processor'
|
4
|
+
|
5
|
+
module Pure
|
6
|
+
module PurePrivate
|
7
|
+
class ExtractorRubyParser < SexpProcessor
|
8
|
+
def initialize(file)
|
9
|
+
super()
|
10
|
+
@file = file
|
11
|
+
@defs = Hash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
process(RubyParser.new.parse(File.read(@file)))
|
16
|
+
@defs
|
17
|
+
end
|
18
|
+
|
19
|
+
def process(sexp)
|
20
|
+
if sexp[0] == :defn
|
21
|
+
name = sexp[1]
|
22
|
+
args = sexp[2].to_a[1..-1]
|
23
|
+
if args.any? { |arg| arg.to_s =~ %r!\A\*! }
|
24
|
+
raise SplatError.new(@file, sexp.line)
|
25
|
+
end
|
26
|
+
@defs[sexp.line] = {
|
27
|
+
:name => name,
|
28
|
+
:args => args,
|
29
|
+
:sexp => sexp.dup,
|
30
|
+
}
|
31
|
+
sexp.clear
|
32
|
+
elsif sexp[0] == :iter and
|
33
|
+
sexp[1][0] == :call and
|
34
|
+
sexp[1][1] == nil and
|
35
|
+
sexp[1][2] == :fun
|
36
|
+
@defs[sexp[1].line] = {
|
37
|
+
:name => :fun,
|
38
|
+
:sexp => [sexp[2], sexp[3]],
|
39
|
+
}
|
40
|
+
sexp.clear
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
require 'pure/pure_private/extractor'
|
3
|
+
require 'pure/pure_private/function_database'
|
4
|
+
require 'pure/pure_private/driver'
|
5
|
+
require 'pure/pure_private/error'
|
6
|
+
|
7
|
+
module Pure
|
8
|
+
module PurePrivate
|
9
|
+
module SingletonFeatures
|
10
|
+
include FunctionDatabase
|
11
|
+
|
12
|
+
def compute(root, opts)
|
13
|
+
Driver.create_instance_and_compute(self, root, opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
def fun(*args, &block)
|
17
|
+
fun_mod = class << self ; @fun_mod ; end
|
18
|
+
node_name, child_names = (
|
19
|
+
if args.size == 1
|
20
|
+
arg = args.first
|
21
|
+
if arg.is_a? Hash
|
22
|
+
unless arg.size == 1
|
23
|
+
raise PurePrivate::ArgumentError,
|
24
|
+
"`fun' given hash of size != 1"
|
25
|
+
end
|
26
|
+
arg.to_a.first
|
27
|
+
else
|
28
|
+
[arg, []]
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise PurePrivate::ArgumentError,
|
32
|
+
"wrong number of arguments (\#{args.size} for 1)"
|
33
|
+
end
|
34
|
+
)
|
35
|
+
child_syms = (
|
36
|
+
if child_names.is_a? Enumerable
|
37
|
+
child_names.map { |t| t.to_sym }
|
38
|
+
else
|
39
|
+
[child_names.to_sym]
|
40
|
+
end
|
41
|
+
)
|
42
|
+
node_sym = node_name.to_sym
|
43
|
+
fun_mod.module_eval {
|
44
|
+
define_method(node_sym, &block)
|
45
|
+
}
|
46
|
+
spec = Extractor.extract(fun_mod, :fun, caller)
|
47
|
+
FUNCTION_DATABASE[fun_mod][node_sym] = spec.merge(
|
48
|
+
:name => node_sym,
|
49
|
+
:args => child_syms,
|
50
|
+
:origin => :fun
|
51
|
+
)
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def method_added(function_name)
|
56
|
+
FUNCTION_DATABASE[self][function_name] = (
|
57
|
+
Extractor.extract(self, function_name, caller).merge(:origin => :def)
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def define_method(*args, &block)
|
62
|
+
raise PurePrivate::NotImplementedError,
|
63
|
+
"define_method called (use the `fun' method instead)"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module Pure
|
3
|
+
module PurePrivate
|
4
|
+
module Util
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def file_line(backtrace)
|
8
|
+
file, line = backtrace.match(%r!\A(.*?):(\d+)!).captures
|
9
|
+
return file, line.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def singleton_class_of(obj)
|
13
|
+
class << obj
|
14
|
+
self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_camel_case(str)
|
19
|
+
str.split('_').map { |t| t.capitalize }.join
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/spec/basic_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
describe "basic computation" do
|
4
|
+
before :all do
|
5
|
+
@mod = pure do
|
6
|
+
def area(width, height)
|
7
|
+
width*height
|
8
|
+
end
|
9
|
+
|
10
|
+
def width(border)
|
11
|
+
20 + border
|
12
|
+
end
|
13
|
+
|
14
|
+
def height(border)
|
15
|
+
30 + border
|
16
|
+
end
|
17
|
+
|
18
|
+
def border
|
19
|
+
5
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
max_threads = 50
|
25
|
+
|
26
|
+
it "should run using compute(:x, :threads => n) syntax" do
|
27
|
+
(1..max_threads).each { |n|
|
28
|
+
@mod.compute(:area, :threads => n).should == (20 + 5)*(30 + 5)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should run using compute(:x, n) syntax" do
|
33
|
+
(1..max_threads).each { |n|
|
34
|
+
@mod.compute(:area, n).should == (20 + 5)*(30 + 5)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
describe "combining pure modules" do
|
4
|
+
before :all do
|
5
|
+
mod_a = @mod_a = pure do
|
6
|
+
def area(width, height)
|
7
|
+
width*height
|
8
|
+
end
|
9
|
+
|
10
|
+
def border
|
11
|
+
5
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
mod_b = @mod_b = pure do
|
16
|
+
def width(border)
|
17
|
+
20 + border
|
18
|
+
end
|
19
|
+
|
20
|
+
def height(border)
|
21
|
+
30 + border
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
combined = @combined = pure do
|
26
|
+
include mod_a
|
27
|
+
include mod_b
|
28
|
+
end
|
29
|
+
|
30
|
+
@combined_override = pure do
|
31
|
+
include combined
|
32
|
+
def border
|
33
|
+
99
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
max_threads = 5
|
39
|
+
|
40
|
+
it "should work with modules included into empty module" do
|
41
|
+
(1..max_threads).each { |n|
|
42
|
+
@combined.compute(:area, n).should == (20 + 5)*(30 + 5)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should work with modules included into overriding module" do
|
47
|
+
(1..max_threads).each { |n|
|
48
|
+
@combined_override.compute(:area, n).should == (20 + 99)*(30 + 99)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should work with one module included into another" do
|
53
|
+
mod_b = @mod_b
|
54
|
+
@mod_a.module_eval {
|
55
|
+
include mod_b
|
56
|
+
}
|
57
|
+
(1..max_threads).each { |n|
|
58
|
+
@mod_a.compute(:area, n).should == (20 + 5)*(30 + 5)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
data/spec/common.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'pure'
|
5
|
+
require 'pathname'
|
6
|
+
require 'spec'
|
7
|
+
|
8
|
+
include Pure
|
9
|
+
|
10
|
+
AVAILABLE_PARSERS = ["ruby_parser"] + (
|
11
|
+
begin
|
12
|
+
require 'ripper'
|
13
|
+
["ripper"]
|
14
|
+
rescue LoadError
|
15
|
+
[]
|
16
|
+
end
|
17
|
+
) + (
|
18
|
+
if Method.instance_methods.include? :parameters
|
19
|
+
[nil]
|
20
|
+
else
|
21
|
+
[]
|
22
|
+
end
|
23
|
+
)
|
24
|
+
|
25
|
+
module Spec::Example::ExampleGroupMethods
|
26
|
+
alias_method :example__original, :example
|
27
|
+
|
28
|
+
def example(*args, &block)
|
29
|
+
AVAILABLE_PARSERS.each { |parser|
|
30
|
+
parser_desc = parser || "no parser"
|
31
|
+
describe "(#{parser_desc})" do
|
32
|
+
before :each do
|
33
|
+
Pure.parser = parser
|
34
|
+
end
|
35
|
+
example__original(*args, &block)
|
36
|
+
end
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
[:it, :specify].each { |method_name|
|
41
|
+
remove_method method_name
|
42
|
+
alias_method method_name, :example
|
43
|
+
}
|
44
|
+
end
|
data/spec/error_spec.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
describe "two defs on the same line:" do
|
4
|
+
it "should raise error unless Method#parameters is used" do
|
5
|
+
code = lambda {
|
6
|
+
pure do
|
7
|
+
def x ; end ; def y ; end
|
8
|
+
end
|
9
|
+
}
|
10
|
+
if Pure.parser.nil?
|
11
|
+
code.should_not raise_error
|
12
|
+
else
|
13
|
+
code.should raise_error(Pure::PurePrivate::ParseError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "function missing:" do
|
19
|
+
it "should raise error" do
|
20
|
+
lambda {
|
21
|
+
pure do
|
22
|
+
def area(width, height)
|
23
|
+
width*height
|
24
|
+
end
|
25
|
+
|
26
|
+
def width
|
27
|
+
33
|
28
|
+
end
|
29
|
+
end.compute :area, :threads => 3
|
30
|
+
}.should raise_error(Pure::PurePrivate::NoFunctionError)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "bad arguments:" do
|
35
|
+
it "should raise error when given nil" do
|
36
|
+
lambda {
|
37
|
+
pure do
|
38
|
+
def f
|
39
|
+
end
|
40
|
+
end.compute nil, 33
|
41
|
+
}.should raise_error(Pure::PurePrivate::ArgumentError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise error when given something random" do
|
45
|
+
lambda {
|
46
|
+
pure do
|
47
|
+
def f
|
48
|
+
end
|
49
|
+
end.compute 33, 33
|
50
|
+
}.should raise_error(Pure::PurePrivate::ArgumentError)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should raise error when given a string" do
|
54
|
+
lambda {
|
55
|
+
pure do
|
56
|
+
def f
|
57
|
+
end
|
58
|
+
end.compute "f", 33
|
59
|
+
}.should raise_error(Pure::PurePrivate::ArgumentError)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "`fun'" do
|
64
|
+
describe "given hash of size != 1" do
|
65
|
+
it "should raise error" do
|
66
|
+
lambda {
|
67
|
+
pure do
|
68
|
+
fun :x => 1, :y => 2 do
|
69
|
+
end
|
70
|
+
end
|
71
|
+
}.should raise_error(Pure::PurePrivate::ArgumentError)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "given more than 1 argument" do
|
76
|
+
it "should raise error" do
|
77
|
+
lambda {
|
78
|
+
pure do
|
79
|
+
fun :x, :y do
|
80
|
+
end
|
81
|
+
end
|
82
|
+
}.should raise_error(Pure::PurePrivate::ArgumentError)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "with &block" do
|
87
|
+
it "should raise error unless Method#parameters is used" do
|
88
|
+
code = lambda {
|
89
|
+
pure do
|
90
|
+
fun :f, &lambda { 33 }
|
91
|
+
end.compute(:f, :threads => 4).should == 33
|
92
|
+
}
|
93
|
+
if Pure.parser.nil?
|
94
|
+
code.should_not raise_error
|
95
|
+
else
|
96
|
+
code.should raise_error(Pure::PurePrivate::ParseError)
|
97
|
+
end
|
98
|
+
|
99
|
+
code = lambda {
|
100
|
+
pure do
|
101
|
+
t = lambda { 33 }
|
102
|
+
fun :f, &t
|
103
|
+
end.compute(:f, :threads => 4).should == 33
|
104
|
+
}
|
105
|
+
if Pure.parser.nil?
|
106
|
+
code.should_not raise_error
|
107
|
+
else
|
108
|
+
code.should raise_error(Pure::PurePrivate::ParseError)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "calling define_method" do
|
115
|
+
it "should raise error" do
|
116
|
+
lambda {
|
117
|
+
pure do
|
118
|
+
define_method :area do |width, height|
|
119
|
+
width*height
|
120
|
+
end
|
121
|
+
|
122
|
+
def width
|
123
|
+
5
|
124
|
+
end
|
125
|
+
|
126
|
+
def height
|
127
|
+
7
|
128
|
+
end
|
129
|
+
end.compute :area, 3
|
130
|
+
}.should raise_error(Pure::PurePrivate::NotImplementedError)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "parse engine" do
|
135
|
+
it "should raise error when not installed" do
|
136
|
+
lambda {
|
137
|
+
Pure.parser = "z"*99
|
138
|
+
}.should raise_error(LoadError)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should raise error when unsupported" do
|
142
|
+
lambda {
|
143
|
+
Pure.parser = "fileutils"
|
144
|
+
}.should raise_error(Pure::PurePrivate::NotImplementedError)
|
145
|
+
end
|
146
|
+
end
|
data/spec/fun_spec.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/common"
|
2
|
+
|
3
|
+
describe "`fun' definitions" do
|
4
|
+
it "should work with symbols only" do
|
5
|
+
pure do
|
6
|
+
fun :area => [:width, :height] do |w, h|
|
7
|
+
w*h
|
8
|
+
end
|
9
|
+
|
10
|
+
fun :width => [:border] do |b|
|
11
|
+
20 + b
|
12
|
+
end
|
13
|
+
|
14
|
+
fun :height => :border do |b|
|
15
|
+
30 + b
|
16
|
+
end
|
17
|
+
|
18
|
+
fun :border do
|
19
|
+
5
|
20
|
+
end
|
21
|
+
end.compute(:area, :threads => 4).should == (20 + 5)*(30 + 5)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should work with symbols and parens only" do
|
25
|
+
pure do
|
26
|
+
fun(:area => [:width, :height]) do |w, h|
|
27
|
+
w*h
|
28
|
+
end
|
29
|
+
|
30
|
+
fun(:width => [:border]) do |b|
|
31
|
+
20 + b
|
32
|
+
end
|
33
|
+
|
34
|
+
fun(:height => :border) do |b|
|
35
|
+
30 + b
|
36
|
+
end
|
37
|
+
|
38
|
+
fun(:border) do
|
39
|
+
5
|
40
|
+
end
|
41
|
+
end.compute(:area, :threads => 4).should == (20 + 5)*(30 + 5)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should work with mixed symbols and strings" do
|
45
|
+
pure do
|
46
|
+
fun :area => [:width, "height"] do |w, h|
|
47
|
+
w*h
|
48
|
+
end
|
49
|
+
|
50
|
+
fun "width" => [:border] do |b|
|
51
|
+
20 + b
|
52
|
+
end
|
53
|
+
|
54
|
+
fun :height => "border" do |b|
|
55
|
+
30 + b
|
56
|
+
end
|
57
|
+
|
58
|
+
fun :border do
|
59
|
+
5
|
60
|
+
end
|
61
|
+
end.compute(:area, :threads => 4).should == (20 + 5)*(30 + 5)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should work with `def' definitions" do
|
65
|
+
pure do
|
66
|
+
fun :width do
|
67
|
+
33
|
68
|
+
end
|
69
|
+
|
70
|
+
def height
|
71
|
+
44
|
72
|
+
end
|
73
|
+
|
74
|
+
fun :area => [:width, :height] do |w, h|
|
75
|
+
w*h
|
76
|
+
end
|
77
|
+
end.compute(:area, 3).should == 33*44
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should be lower precedence than `def' definitions" do
|
81
|
+
pure do
|
82
|
+
fun :f do
|
83
|
+
44
|
84
|
+
end
|
85
|
+
end.compute(:f, 10).should == 44
|
86
|
+
|
87
|
+
pure do
|
88
|
+
fun :f do
|
89
|
+
44
|
90
|
+
end
|
91
|
+
|
92
|
+
def f
|
93
|
+
33
|
94
|
+
end
|
95
|
+
end.compute(:f, 10).should == 33
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should support splat in block args" do
|
99
|
+
pure do
|
100
|
+
fun :area => [:width, :height] do |*a|
|
101
|
+
a[0]*a[1]
|
102
|
+
end
|
103
|
+
|
104
|
+
def width
|
105
|
+
3
|
106
|
+
end
|
107
|
+
|
108
|
+
def height
|
109
|
+
4
|
110
|
+
end
|
111
|
+
end.compute(:area, 3).should == 12
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should support splat with single-element array" do
|
115
|
+
pure do
|
116
|
+
name = [:f]
|
117
|
+
fun(*name) do
|
118
|
+
name
|
119
|
+
end
|
120
|
+
end.compute(:f, 3).should == [:f]
|
121
|
+
end
|
122
|
+
end
|