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