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.
- data/CHANGES.rdoc +7 -0
- data/MANIFEST +44 -20
- data/README.rdoc +553 -16
- data/Rakefile +25 -2
- data/devel/jumpstart.rb +606 -253
- data/install.rb +1 -2
- data/lib/pure.rb +38 -16
- data/lib/pure/bundled_parsers.rb +4 -0
- data/lib/pure/bundled_plugin.rb +49 -0
- data/lib/pure/compiler/ruby_parser.rb +63 -0
- data/lib/pure/delegate.rb +16 -0
- data/lib/pure/driver.rb +33 -0
- data/lib/pure/dsl.rb +2 -0
- data/lib/pure/dsl_definition.rb +11 -0
- data/lib/pure/error.rb +89 -0
- data/lib/pure/extracted_functions.rb +11 -0
- data/lib/pure/extractor.rb +59 -0
- data/lib/pure/names.rb +9 -0
- data/lib/pure/native_worker.rb +27 -0
- data/lib/pure/parser/impl/base_parser.rb +21 -0
- data/lib/pure/parser/impl/internal.rb +31 -0
- data/lib/pure/parser/impl/ripper.rb +96 -0
- data/lib/pure/parser/impl/ruby_parser.rb +77 -0
- data/lib/pure/parser/internal.rb +4 -0
- data/lib/pure/parser/ripper.rb +2 -0
- data/lib/pure/parser/ruby_parser.rb +2 -0
- data/lib/pure/pure.rb +32 -0
- data/lib/pure/pure_module.rb +141 -0
- data/lib/pure/util.rb +15 -0
- data/lib/pure/version.rb +4 -0
- data/spec/compiler_ruby_parser_spec.rb +79 -0
- data/spec/compute_overrides_spec.rb +99 -0
- data/spec/compute_spec.rb +86 -0
- data/spec/compute_thread_spec.rb +29 -0
- data/spec/compute_timed_spec.rb +40 -0
- data/spec/delegate_spec.rb +141 -0
- data/spec/fstat_example.rb +26 -0
- data/spec/parser_sexp_spec.rb +100 -0
- data/spec/parser_spec.rb +18 -31
- data/spec/pure_combine_spec.rb +77 -0
- data/spec/pure_def_spec.rb +186 -0
- data/spec/pure_define_method_spec.rb +24 -0
- data/spec/pure_eval_spec.rb +18 -0
- data/spec/pure_fun_spec.rb +243 -0
- data/spec/pure_nested_spec.rb +35 -0
- data/spec/pure_parser_spec.rb +50 -0
- data/spec/pure_spec.rb +81 -0
- data/spec/pure_spec_base.rb +106 -0
- data/spec/pure_splat_spec.rb +18 -0
- data/spec/pure_two_defs_spec.rb +20 -0
- data/spec/pure_worker_spec.rb +33 -0
- data/spec/readme_spec.rb +36 -32
- data/spec/splat_spec.rb +12 -11
- data/spec/worker_spec.rb +89 -0
- metadata +157 -41
- data/devel/jumpstart/lazy_attribute.rb +0 -38
- data/devel/jumpstart/ruby.rb +0 -44
- data/devel/jumpstart/simple_installer.rb +0 -85
- data/lib/pure/pure_private/creator.rb +0 -27
- data/lib/pure/pure_private/driver.rb +0 -48
- data/lib/pure/pure_private/error.rb +0 -32
- data/lib/pure/pure_private/extractor.rb +0 -79
- data/lib/pure/pure_private/extractor_ripper.rb +0 -95
- data/lib/pure/pure_private/extractor_ruby_parser.rb +0 -47
- data/lib/pure/pure_private/function_database.rb +0 -10
- data/lib/pure/pure_private/singleton_features.rb +0 -67
- data/lib/pure/pure_private/util.rb +0 -23
- data/spec/basic_spec.rb +0 -38
- data/spec/combine_spec.rb +0 -62
- data/spec/common.rb +0 -44
- data/spec/error_spec.rb +0 -146
- data/spec/fun_spec.rb +0 -122
- data/spec/lazy_spec.rb +0 -22
- data/spec/subseqent_spec.rb +0 -42
- 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
|
data/lib/pure/pure.rb
ADDED
@@ -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
|
data/lib/pure/util.rb
ADDED
@@ -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
|
data/lib/pure/version.rb
ADDED
@@ -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
|
+
|