pure 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/CHANGES.rdoc +7 -0
  2. data/MANIFEST +44 -20
  3. data/README.rdoc +553 -16
  4. data/Rakefile +25 -2
  5. data/devel/jumpstart.rb +606 -253
  6. data/install.rb +1 -2
  7. data/lib/pure.rb +38 -16
  8. data/lib/pure/bundled_parsers.rb +4 -0
  9. data/lib/pure/bundled_plugin.rb +49 -0
  10. data/lib/pure/compiler/ruby_parser.rb +63 -0
  11. data/lib/pure/delegate.rb +16 -0
  12. data/lib/pure/driver.rb +33 -0
  13. data/lib/pure/dsl.rb +2 -0
  14. data/lib/pure/dsl_definition.rb +11 -0
  15. data/lib/pure/error.rb +89 -0
  16. data/lib/pure/extracted_functions.rb +11 -0
  17. data/lib/pure/extractor.rb +59 -0
  18. data/lib/pure/names.rb +9 -0
  19. data/lib/pure/native_worker.rb +27 -0
  20. data/lib/pure/parser/impl/base_parser.rb +21 -0
  21. data/lib/pure/parser/impl/internal.rb +31 -0
  22. data/lib/pure/parser/impl/ripper.rb +96 -0
  23. data/lib/pure/parser/impl/ruby_parser.rb +77 -0
  24. data/lib/pure/parser/internal.rb +4 -0
  25. data/lib/pure/parser/ripper.rb +2 -0
  26. data/lib/pure/parser/ruby_parser.rb +2 -0
  27. data/lib/pure/pure.rb +32 -0
  28. data/lib/pure/pure_module.rb +141 -0
  29. data/lib/pure/util.rb +15 -0
  30. data/lib/pure/version.rb +4 -0
  31. data/spec/compiler_ruby_parser_spec.rb +79 -0
  32. data/spec/compute_overrides_spec.rb +99 -0
  33. data/spec/compute_spec.rb +86 -0
  34. data/spec/compute_thread_spec.rb +29 -0
  35. data/spec/compute_timed_spec.rb +40 -0
  36. data/spec/delegate_spec.rb +141 -0
  37. data/spec/fstat_example.rb +26 -0
  38. data/spec/parser_sexp_spec.rb +100 -0
  39. data/spec/parser_spec.rb +18 -31
  40. data/spec/pure_combine_spec.rb +77 -0
  41. data/spec/pure_def_spec.rb +186 -0
  42. data/spec/pure_define_method_spec.rb +24 -0
  43. data/spec/pure_eval_spec.rb +18 -0
  44. data/spec/pure_fun_spec.rb +243 -0
  45. data/spec/pure_nested_spec.rb +35 -0
  46. data/spec/pure_parser_spec.rb +50 -0
  47. data/spec/pure_spec.rb +81 -0
  48. data/spec/pure_spec_base.rb +106 -0
  49. data/spec/pure_splat_spec.rb +18 -0
  50. data/spec/pure_two_defs_spec.rb +20 -0
  51. data/spec/pure_worker_spec.rb +33 -0
  52. data/spec/readme_spec.rb +36 -32
  53. data/spec/splat_spec.rb +12 -11
  54. data/spec/worker_spec.rb +89 -0
  55. metadata +157 -41
  56. data/devel/jumpstart/lazy_attribute.rb +0 -38
  57. data/devel/jumpstart/ruby.rb +0 -44
  58. data/devel/jumpstart/simple_installer.rb +0 -85
  59. data/lib/pure/pure_private/creator.rb +0 -27
  60. data/lib/pure/pure_private/driver.rb +0 -48
  61. data/lib/pure/pure_private/error.rb +0 -32
  62. data/lib/pure/pure_private/extractor.rb +0 -79
  63. data/lib/pure/pure_private/extractor_ripper.rb +0 -95
  64. data/lib/pure/pure_private/extractor_ruby_parser.rb +0 -47
  65. data/lib/pure/pure_private/function_database.rb +0 -10
  66. data/lib/pure/pure_private/singleton_features.rb +0 -67
  67. data/lib/pure/pure_private/util.rb +0 -23
  68. data/spec/basic_spec.rb +0 -38
  69. data/spec/combine_spec.rb +0 -62
  70. data/spec/common.rb +0 -44
  71. data/spec/error_spec.rb +0 -146
  72. data/spec/fun_spec.rb +0 -122
  73. data/spec/lazy_spec.rb +0 -22
  74. data/spec/subseqent_spec.rb +0 -42
  75. data/spec/timed_spec.rb +0 -30
@@ -0,0 +1,21 @@
1
+
2
+ module Pure
3
+ module Parser
4
+ module BaseParser
5
+ @cache = Hash.new { |hash, key| hash[key] = Hash.new }
6
+
7
+ class << self
8
+ def extract(mod, method_name, file, line, processor)
9
+ defs = @cache[processor][file] || (
10
+ @cache[processor][file] = processor.new(file).run
11
+ )
12
+ spec = defs[line]
13
+ unless spec and spec[:name] and spec[:name] == method_name
14
+ raise ParseMethodError.new(file, line, method_name)
15
+ end
16
+ spec.merge(:file => file, :line => line)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+
2
+ module Pure
3
+ module Parser
4
+ module Internal
5
+ module_function
6
+
7
+ def extract(mod, method_name, file, line)
8
+ if method_name == :__fun
9
+ Hash.new
10
+ else
11
+ parameters = mod.instance_method(method_name).parameters.reject {
12
+ |type, name|
13
+ type == :block
14
+ }
15
+ args = parameters.map { |type, name|
16
+ name
17
+ }
18
+ types = parameters.inject(Hash.new) { |acc, (type, name)|
19
+ acc.merge!(type => true)
20
+ }
21
+ {
22
+ :name => method_name,
23
+ :args => args,
24
+ :splat => types[:rest],
25
+ :default => types[:opt],
26
+ }
27
+ end.merge(:file => file, :line => line)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,96 @@
1
+
2
+ require 'ripper'
3
+ require 'pure/parser/impl/base_parser'
4
+
5
+ module Pure
6
+ module Parser
7
+ module Ripper
8
+ class << self
9
+ def extract(mod, method_name, file, line)
10
+ BaseParser.extract(mod, method_name, file, line, Processor)
11
+ end
12
+ end
13
+
14
+ class Processor
15
+ def initialize(file)
16
+ @file = file
17
+ @defs = Hash.new
18
+ end
19
+
20
+ def run
21
+ process(::Ripper.sexp(File.read(@file)))
22
+ @defs
23
+ end
24
+
25
+ def process_def(sexp)
26
+ if sexp[0] == :def
27
+ name = sexp[1][1].to_sym
28
+ line = sexp[1][2][0]
29
+ params = (
30
+ case sexp[2].first
31
+ when :params
32
+ sexp[2]
33
+ when :paren
34
+ sexp[2][1]
35
+ #else
36
+ # raise ParseError.new(@file, line, "unforeseen `def' syntax"
37
+ end
38
+ )
39
+ args = (
40
+ if params[1].nil?
41
+ []
42
+ else
43
+ params[1].map { |t| t[1].to_sym }
44
+ end
45
+ )
46
+ splat = params.any? { |t| t.is_a?(Array) and t[0] == :rest_param }
47
+ default = !!params[2]
48
+ @defs[line] = {
49
+ :name => name,
50
+ :args => args,
51
+ :code => sexp,
52
+ :splat => splat,
53
+ :default => default,
54
+ }
55
+ end
56
+ end
57
+
58
+ def process_fun(sexp)
59
+ if sexp[0] == :method_add_block and sexp[1].is_a?(Array)
60
+ line = (
61
+ if sexp[1][0] == :command and
62
+ sexp[1][1].is_a?(Array) and
63
+ sexp[1][1][1] == "fun"
64
+ sexp[1][1][2][0]
65
+ elsif sexp[1][0] == :method_add_arg and
66
+ sexp[1][1].is_a?(Array) and
67
+ sexp[1][1][0] == :fcall and
68
+ sexp[1][1][1].is_a?(Array) and
69
+ sexp[1][1][1][1] == "fun"
70
+ sexp[1][1][1][2][0]
71
+ else
72
+ nil
73
+ end
74
+ )
75
+ if line
76
+ @defs[line] = {
77
+ :name => :__fun,
78
+ :code => sexp[2],
79
+ }
80
+ end
81
+ end
82
+ end
83
+
84
+ def process(sexp)
85
+ if sexp.is_a? Array
86
+ process_def(sexp)
87
+ process_fun(sexp)
88
+ sexp.each { |sub_sexp|
89
+ process(sub_sexp)
90
+ }
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,77 @@
1
+
2
+ require 'ruby_parser'
3
+ require 'sexp_processor'
4
+ require 'pure/parser/impl/base_parser'
5
+
6
+ module Pure
7
+ module Parser
8
+ module RubyParser
9
+ class << self
10
+ def extract(mod, method_name, file, line)
11
+ BaseParser.extract(mod, method_name, file, line, Processor)
12
+ end
13
+
14
+ def compiler
15
+ %w[pure/compiler/ruby_parser Pure::Compiler::RubyParser]
16
+ end
17
+ end
18
+
19
+ module DupSexp
20
+ module_function
21
+ def dup_sexp(sexp)
22
+ if sexp.is_a? Sexp or sexp.is_a? Array
23
+ array = sexp.map { |sub_sexp|
24
+ dup_sexp(sub_sexp)
25
+ }
26
+ Sexp.new.replace(array)
27
+ else
28
+ sexp
29
+ end
30
+ end
31
+ end
32
+
33
+ class Processor < SexpProcessor
34
+ include DupSexp
35
+
36
+ def initialize(file)
37
+ super()
38
+ @file = file
39
+ @defs = Hash.new
40
+ end
41
+
42
+ def run
43
+ process(::RubyParser.new.parse(File.read(@file)))
44
+ @defs
45
+ end
46
+
47
+ def process(sexp)
48
+ if sexp[0] == :defn
49
+ name = sexp[1]
50
+ args = dup_sexp(sexp[2][1..-1]).to_a
51
+ args.reject! { |a| a.to_s =~ %r!\A&! }
52
+ default = (
53
+ args.last and args.last.is_a?(Array) and args.last[0] == :block
54
+ )
55
+ splat = args.any? { |arg| arg.to_s =~ %r!\A\*! }
56
+ @defs[sexp.line] = {
57
+ :name => name,
58
+ :args => args,
59
+ :code => dup_sexp(sexp),
60
+ :splat => splat,
61
+ :default => default,
62
+ }
63
+ elsif sexp[0] == :iter and
64
+ sexp[1][0] == :call and
65
+ sexp[1][1] == nil and
66
+ sexp[1][2] == :fun
67
+ @defs[sexp[1].line] = {
68
+ :name => :__fun,
69
+ :code => dup_sexp(sexp)
70
+ }
71
+ end
72
+ super
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,4 @@
1
+ # rcov hack
2
+ has_parameters = Method.instance_methods.include?(:parameters)
3
+ require('pure/parser/impl/internal') if has_parameters
4
+ raise LoadError unless has_parameters
@@ -0,0 +1,2 @@
1
+ # rcov hack
2
+ require('ripper') ; require('pure/parser/impl/ripper')
@@ -0,0 +1,2 @@
1
+ # rcov hack
2
+ require('ruby_parser') ; require('pure/parser/impl/ruby_parser')
@@ -0,0 +1,32 @@
1
+
2
+ module Pure
3
+ class << self
4
+ #
5
+ # Create a <em>pure module</em>. The given block is evaluated in
6
+ # the context of the created module.
7
+ #
8
+ # A pure module is a Module instance whose methods have been
9
+ # specially registered for lexical analysis.
10
+ #
11
+ # The methods of a pure module are referred to as <em>pure
12
+ # functions</em>.
13
+ #
14
+ # See Pure::PureModule.
15
+ #
16
+ def define(parser = Pure.parser, &block)
17
+ PureModule.new(parser, &block)
18
+ end
19
+
20
+ attr_accessor :parser, :worker
21
+
22
+ remove_method :parser
23
+ def parser #:nodoc:
24
+ @parser ||= BundledParsers.find_default
25
+ end
26
+
27
+ remove_method :worker
28
+ def worker #:nodoc:
29
+ @worker ||= NativeWorker
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,141 @@
1
+
2
+ module Pure
3
+ class PureModule < Module
4
+ def initialize(parser, &block) #:nodoc:
5
+ @parsing_active = true
6
+ @parser = parser
7
+ super(&block)
8
+ end
9
+
10
+ #
11
+ # call-seq: compute(overrides = {})
12
+ # compute(num_parallel, overrides = {})
13
+ # compute(worker, overrides = {})
14
+ #
15
+ # Initialize a computation.
16
+ #
17
+ # All three forms take an optional hash for overriding pure
18
+ # functions.
19
+ #
20
+ # In the first form, Pure.worker is the worker for the computation
21
+ # and Pure.worker decides the number of parallel computations.
22
+ #
23
+ # In the second form, Pure.worker is the worker for the
24
+ # computation and _num_parallel_ is passed as a hint to
25
+ # Pure.worker, which may accept or ignore the hint.
26
+ #
27
+ # In the third form, _worker_ is the worker for the computation
28
+ # and _worker_ decides the number of parallel computations.
29
+ #
30
+ def compute(*args)
31
+ overrides = args.last.is_a?(Hash) ? args.pop : Hash.new
32
+ worker, num_parallel = (
33
+ case args.size
34
+ when 0
35
+ [Pure.worker, nil]
36
+ when 1
37
+ if args[0].is_a? Integer
38
+ [Pure.worker, args[0]]
39
+ else
40
+ [args[0], nil]
41
+ end
42
+ else
43
+ raise ArgumentError, "wrong number of arguments"
44
+ end
45
+ )
46
+ driver = Driver.new(worker, self, num_parallel, overrides)
47
+ delegate = Delegate.new(driver)
48
+ if block_given?
49
+ yield delegate
50
+ else
51
+ delegate
52
+ end
53
+ end
54
+
55
+ #
56
+ # call-seq: fun name => [name_a, name_b,...] do |arg_a, arg_b,...|
57
+ # ...
58
+ # end
59
+ #
60
+ # Define a pure function whose name and/or argument names are
61
+ # not known at compile time.
62
+ #
63
+ # The name of the pure function is the value of _name_. The
64
+ # names of the function arguments are _name_a_, _name_b_,...
65
+ # The respective values of the function arguments are
66
+ # _arg_a_,_arg_b_,...
67
+ #
68
+ # See README.rdoc for examples.
69
+ #
70
+ def fun(*args, &block)
71
+ function_str, arg_data = (
72
+ if args.size == 1
73
+ arg = args.first
74
+ if arg.is_a? Hash
75
+ unless arg.size == 1
76
+ raise ArgumentError, "`fun' given hash of size != 1"
77
+ end
78
+ arg.to_a.first
79
+ else
80
+ [arg, []]
81
+ end
82
+ else
83
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 1)"
84
+ end
85
+ )
86
+ arg_names = (
87
+ if arg_data.is_a? Enumerable
88
+ arg_data.map { |t| t.to_sym }
89
+ else
90
+ [arg_data.to_sym]
91
+ end
92
+ )
93
+ function_name = function_str.to_sym
94
+ deactivate_parsing {
95
+ define_method(function_name, &block)
96
+ }
97
+ Extractor.record_function(self, :fun, function_name, arg_names, caller)
98
+ nil
99
+ end
100
+
101
+ def method_added(function_name) #:nodoc:
102
+ super
103
+ if @parsing_active
104
+ Extractor.record_function(self, :def, function_name, nil, caller)
105
+ end
106
+ end
107
+
108
+ def define_method(*args, &block) #:nodoc:
109
+ if @parsing_active
110
+ raise DefineMethodError.new(*Util.file_line(caller.first))
111
+ else
112
+ super
113
+ end
114
+ end
115
+
116
+ attr_reader :parser #:nodoc:
117
+
118
+ def deactivate_parsing #:nodoc:
119
+ @parsing_active = false
120
+ begin
121
+ yield
122
+ ensure
123
+ @parsing_active = true
124
+ end
125
+ end
126
+
127
+ def each_function #:nodoc:
128
+ ancestors.each { |ancestor|
129
+ if defs = ExtractedFunctions[parser][ancestor]
130
+ defs.each_pair { |name, spec|
131
+ yield name, spec
132
+ }
133
+ end
134
+ }
135
+ end
136
+
137
+ # want 'fun' both documented and private; rdoc --all is bad
138
+ rdoc_fun = :fun
139
+ private rdoc_fun
140
+ end
141
+ end
@@ -0,0 +1,15 @@
1
+
2
+ module Pure
3
+ module Util
4
+ module_function
5
+
6
+ def file_line(backtrace)
7
+ file, line = backtrace.match(%r!\A(.*?):(\d+)!).captures
8
+ return file, line.to_i
9
+ end
10
+
11
+ def to_camel_case(str)
12
+ str.split('_').map { |t| t.capitalize }.join
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+
2
+ module Pure
3
+ VERSION = "0.2.0"
4
+ end
@@ -0,0 +1,79 @@
1
+ require File.dirname(__FILE__) + '/pure_spec_base'
2
+
3
+ require 'pure/parser/ruby_parser'
4
+ require 'pure/compiler/ruby_parser'
5
+
6
+ describe Pure::Compiler::RubyParser do
7
+ before :all do
8
+ @compiler = Pure::Compiler::RubyParser.new
9
+ end
10
+
11
+ it "should transform `fun' definitions to `define_method' definitions" do
12
+ def_f = pure(Pure::Parser::RubyParser) do
13
+ def f(x, y)
14
+ x + y
15
+ end
16
+ end
17
+
18
+ fun_f = pure(Pure::Parser::RubyParser) do
19
+ fun :f => [:x, :y] do |a, b|
20
+ a + b
21
+ end
22
+ end
23
+
24
+ def_g = pure(Pure::Parser::RubyParser) do
25
+ def g
26
+ end
27
+ end
28
+
29
+ fun_g = pure(Pure::Parser::RubyParser) do
30
+ fun :g do
31
+ end
32
+ end
33
+
34
+ def_h = pure(Pure::Parser::RubyParser) do
35
+ def h(x)
36
+ x**2
37
+ end
38
+ end
39
+
40
+ fun_h = pure(Pure::Parser::RubyParser) do
41
+ fun :h => :x do |a|
42
+ a**2
43
+ end
44
+ end
45
+
46
+ fun_i = pure(Pure::Parser::RubyParser) do
47
+ fun :i => [:p, :q] do |*s|
48
+ s.size
49
+ end
50
+ end
51
+
52
+ fun_j = pure(Pure::Parser::RubyParser) do
53
+ fun :j => [:p, :q] do |r, *s|
54
+ r + s
55
+ end
56
+ end
57
+
58
+ [
59
+ [:def, def_f, :f, "def f(x, y) (x + y) end"],
60
+ [:fun, fun_f, :f, "define_method(:f) { |a, b| (a + b) }"],
61
+ [:def, def_g, :g, "def g # do nothing end"],
62
+ [:fun, fun_g, :g, "define_method(:g) { }"],
63
+ [:def, def_h, :h, "def h(x) (x ** 2) end"],
64
+ [:fun, fun_h, :h, "define_method(:h) { |a| (a ** 2) }"],
65
+ [:fun, fun_i, :i, "define_method(:i) { |*s| s.size }"],
66
+ [:fun, fun_j, :j, "define_method(:j) { |r, *s| (r + s) }"],
67
+ ].each { |type, mod, name, expected|
68
+ entry = Pure::ExtractedFunctions[Pure::Parser::RubyParser][mod][name]
69
+ entry[:code].should be_a(Sexp)
70
+ sexp = Pure::Parser::RubyParser::DupSexp.dup_sexp(entry[:code])
71
+ if entry[:origin] == :fun
72
+ sexp = @compiler.fun_to_define_method(entry[:name], sexp)
73
+ end
74
+ recovered = Ruby2Ruby.new.process(sexp).strip.gsub(%r!\s+!, " ")
75
+ recovered.should == expected
76
+ }
77
+ end
78
+ end
79
+