pure 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,10 @@
1
+
2
+ module Pure
3
+ module PurePrivate
4
+ module FunctionDatabase
5
+ FUNCTION_DATABASE = Hash.new { |hash, key|
6
+ hash[key] = Hash.new
7
+ }
8
+ end
9
+ end
10
+ 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
@@ -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
+
@@ -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
@@ -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
@@ -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