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.
@@ -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