pure 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|