rewrite 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +2 -0
- data/lib/rewrite/def_var.rb +18 -0
- data/lib/rewrite/evaluation_strategies.rb +85 -3
- data/lib/rewrite/prelude/called_by_name.rb +30 -4
- data/lib/rewrite/variables.rb +16 -1
- data/lib/rewrite/version.rb +2 -2
- data/lib/rewrite/with.rb +10 -2
- data/lib/rewrite.rb +4 -0
- data/test/test_call_by_name.rb +45 -0
- data/test/test_call_splatted_by_name.rb +45 -0
- data/test/test_rewriter_helpers.rb +0 -11
- data/website/index.html +8 -2
- data/website/index.txt +4 -0
- metadata +4 -3
data/History.txt
CHANGED
data/lib/rewrite/def_var.rb
CHANGED
@@ -6,6 +6,24 @@ require 'sexp_processor'
|
|
6
6
|
|
7
7
|
module Rewrite
|
8
8
|
|
9
|
+
# Hygienically define a variable to be used in a chunk of code.
|
10
|
+
#
|
11
|
+
# Defvar.new(:name, definition).process(exp)
|
12
|
+
# => lambda { |name|
|
13
|
+
# exp
|
14
|
+
# }.call(definition)
|
15
|
+
#
|
16
|
+
# This is useful when you want to wrap some code in a let.
|
17
|
+
#
|
18
|
+
# There's an olptimization so that you can nest it without penalty:
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# Defvar.new(:name1, definition1).process(
|
22
|
+
# Defvar.new(:name2, definition2).process(exp)
|
23
|
+
# ) => lambda { |name1, name2|
|
24
|
+
# exp
|
25
|
+
# }.call(definition1, definition2)
|
26
|
+
#
|
9
27
|
class DefVar
|
10
28
|
|
11
29
|
def initialize(sym, sexp)
|
@@ -6,6 +6,13 @@ require 'sexp_processor'
|
|
6
6
|
|
7
7
|
module Rewrite
|
8
8
|
|
9
|
+
# Takes a sexp representing a lambda and rewrites all of its parameter references as thunk calls.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# lambda { |a,b| a + b }
|
14
|
+
# => lambda { |a,b| a.call + b.call }
|
15
|
+
#
|
9
16
|
class RewriteParametersAsThunkCalls
|
10
17
|
|
11
18
|
def sexp(exp)
|
@@ -41,10 +48,15 @@ module Rewrite
|
|
41
48
|
end
|
42
49
|
|
43
50
|
# Initialize with a list of names, e.g.
|
44
|
-
#
|
51
|
+
# CallByThunk.new(:foo, :bar)
|
52
|
+
#
|
53
|
+
# It then converts expressions of the form:
|
54
|
+
#
|
55
|
+
# foo(expr1, expr2, ..., exprn)
|
56
|
+
#
|
57
|
+
# into:
|
45
58
|
#
|
46
|
-
#
|
47
|
-
# foo.call( lambda { expr1 }, lambda { expr2 }, ..., lambda { exprn })
|
59
|
+
# foo.call( lambda { expr1 }, lambda { expr2 }, ..., lambda { exprn })
|
48
60
|
#
|
49
61
|
# This is handy when combined with RewriteVariablesAsThunkCalls in the following
|
50
62
|
# manner: if you rewrite function invocations with CallByThunk and also rewrite the
|
@@ -82,5 +94,75 @@ module Rewrite
|
|
82
94
|
end
|
83
95
|
|
84
96
|
end
|
97
|
+
|
98
|
+
# Initialize with a list of names, e.g.
|
99
|
+
#
|
100
|
+
# CallSplattedByThunk.new(:foo, :bar)
|
101
|
+
#
|
102
|
+
# It then converts expressions of the form:
|
103
|
+
#
|
104
|
+
# foo(expr1, expr2, ..., exprn)
|
105
|
+
#
|
106
|
+
# into:
|
107
|
+
#
|
108
|
+
# foo.call(
|
109
|
+
# CallSplattedByThunk::Parameters.new(
|
110
|
+
# lambda { expr1 }, lambda { expr2 }, ..., lambda { exprn }
|
111
|
+
# )
|
112
|
+
# )
|
113
|
+
#
|
114
|
+
# This is allows you to create call-by-name pseudo-functions that take
|
115
|
+
# an arbitrary number of arguments
|
116
|
+
#
|
117
|
+
class CallSplattedByThunk < SexpProcessor
|
118
|
+
|
119
|
+
def initialize(*functions_to_splat_thunkify)
|
120
|
+
@functions_to_splat_thunkify = functions_to_splat_thunkify
|
121
|
+
super()
|
122
|
+
end
|
123
|
+
|
124
|
+
def process_fcall(exp)
|
125
|
+
qua = exp.dup
|
126
|
+
exp.shift
|
127
|
+
name = exp.shift
|
128
|
+
if @functions_to_splat_thunkify.include? name
|
129
|
+
thunked = s(:call, s(:dvar, name), :call)
|
130
|
+
unless exp.empty?
|
131
|
+
arguments = exp.shift
|
132
|
+
raise "Do not understand arguments #{arguments}" unless arguments[0] == :array
|
133
|
+
thunked << s(:array,
|
134
|
+
s(:call,
|
135
|
+
s(:colon2, s(:colon2, s(:const, :Rewrite), :CallSplattedByThunk), :Parameters),
|
136
|
+
:new,
|
137
|
+
arguments[1..-1].inject(s(:array)) { |arr, arg|
|
138
|
+
arr << s(:iter, s(:fcall, :lambda), nil, process(arg))
|
139
|
+
}
|
140
|
+
)
|
141
|
+
)
|
142
|
+
end
|
143
|
+
else
|
144
|
+
thunked = s(:fcall, name) # :fcall, name_of_the_function
|
145
|
+
thunked << process(exp.shift) unless exp.empty?
|
146
|
+
end
|
147
|
+
thunked
|
148
|
+
end
|
149
|
+
|
150
|
+
class Parameters
|
151
|
+
|
152
|
+
include Enumerable
|
153
|
+
|
154
|
+
def initialize(*lambdas)
|
155
|
+
@lambdas = *lambdas
|
156
|
+
end
|
157
|
+
|
158
|
+
def each
|
159
|
+
lambdas.each do |l|
|
160
|
+
yield l.call
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
85
167
|
|
86
168
|
end
|
@@ -2,7 +2,7 @@ module Rewrite
|
|
2
2
|
|
3
3
|
module Prelude
|
4
4
|
|
5
|
-
#
|
5
|
+
# See "Macros, Hygiene, and Call By Name in Ruby":http://weblog.raganwald.com/2008/06/macros-hygiene-and-call-by-name-in-ruby.html
|
6
6
|
#
|
7
7
|
# CalledByName allows you to define your own function-like-things that are called by name rather than
|
8
8
|
# by value. Here is a trivial example:
|
@@ -29,7 +29,7 @@ module Rewrite
|
|
29
29
|
#
|
30
30
|
# Second, and this is the funky bit, parameters inside of your function-like-thing are called by name. Meaning,
|
31
31
|
# when you call your function-like-thing, instead of evaluating each parameter's expression and passing its
|
32
|
-
# value, Ruby passes the expression itself. The expression is not evaluated until the parpameter is actually used,
|
32
|
+
# value, Ruby passes the a lambda containing expression itself. The expression is not evaluated until the parpameter is actually used,
|
33
33
|
# and if you use it more than once the expression is evaluated more than once. This matters when there are side
|
34
34
|
# effects.
|
35
35
|
#
|
@@ -75,10 +75,36 @@ module Rewrite
|
|
75
75
|
# )
|
76
76
|
#
|
77
77
|
class CalledByName
|
78
|
+
|
79
|
+
def splatted?(proc)
|
80
|
+
sexp = proc.to_sexp
|
81
|
+
raise "Expected a proc" unless sexp[0] == :proc
|
82
|
+
raise "Expected a proc with arity >= 1" if sexp[1] == nil
|
83
|
+
arguments = sexp[1]
|
84
|
+
arguments[0] == :masgn && arguments[1] && arguments[1].respond_to?(:first) && arguments[1].first != :array
|
85
|
+
end
|
86
|
+
|
87
|
+
def as_lambda(proc_sexp)
|
88
|
+
s(:iter,
|
89
|
+
s(:fcall, :lambda),
|
90
|
+
*proc_sexp[1..-1]
|
91
|
+
)
|
92
|
+
end
|
78
93
|
|
79
94
|
def initialize(name, &proc)
|
80
|
-
|
81
|
-
|
95
|
+
if splatted?(proc)
|
96
|
+
@let = Rewrite::DefVar.new(
|
97
|
+
name,
|
98
|
+
as_lambda(proc.to_sexp)
|
99
|
+
)
|
100
|
+
@call_by_thunk = Rewrite::CallSplattedByThunk.new(name)
|
101
|
+
else
|
102
|
+
@let = Rewrite::DefVar.new(
|
103
|
+
name,
|
104
|
+
Rewrite::RewriteParametersAsThunkCalls.new.process(eval(proc.to_sexp.inspect))
|
105
|
+
)
|
106
|
+
@call_by_thunk = Rewrite::CallByThunk.new(name)
|
107
|
+
end
|
82
108
|
end
|
83
109
|
|
84
110
|
def process(exp)
|
data/lib/rewrite/variables.rb
CHANGED
@@ -6,7 +6,22 @@ require 'sexp_processor'
|
|
6
6
|
require 'sexp'
|
7
7
|
|
8
8
|
module Rewrite
|
9
|
-
|
9
|
+
|
10
|
+
#--
|
11
|
+
#
|
12
|
+
# TODO: Implement splat variables somehow
|
13
|
+
#
|
14
|
+
# def test_splat_variables
|
15
|
+
#
|
16
|
+
# assert_equal(
|
17
|
+
# lambda { |*a| a[0].call }.to_sexp.to_a,
|
18
|
+
# Rewrite::RewriteVariablesAsThunkCalls.new(:a).process(
|
19
|
+
# lambda { |*a| a[0] }.to_sexp
|
20
|
+
# ).to_a
|
21
|
+
# )
|
22
|
+
#
|
23
|
+
# end
|
24
|
+
|
10
25
|
class VariableRewriter < SexpProcessor
|
11
26
|
|
12
27
|
attr_reader :replacement_sexp
|
data/lib/rewrite/version.rb
CHANGED
data/lib/rewrite/with.rb
CHANGED
@@ -8,6 +8,11 @@ module Rewrite
|
|
8
8
|
module With
|
9
9
|
|
10
10
|
def self.with(*sexp_processors, &body)
|
11
|
+
ruby = expand(*sexp_processors, &body)
|
12
|
+
eval(ruby, body.binding())
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.expand(*sexp_processors, &body)
|
11
16
|
rewritten = sexp_processors.flatten.inject(body.to_sexp.last) { |sexp, sexp_processor_parameter|
|
12
17
|
if sexp_processor_parameter.respond_to?(:new) && sexp_processor_parameter.kind_of?(Class)
|
13
18
|
sexp_processor = sexp_processor_parameter.new
|
@@ -17,8 +22,7 @@ module Rewrite
|
|
17
22
|
sexp_processor.process(sexp)
|
18
23
|
}
|
19
24
|
rewritten = eval(rewritten.to_s) # i don't know why i need this!!
|
20
|
-
|
21
|
-
eval(ruby, body.binding())
|
25
|
+
Ruby2Ruby.new.process(rewritten)
|
22
26
|
end
|
23
27
|
|
24
28
|
module ClassMethods
|
@@ -27,6 +31,10 @@ module Rewrite
|
|
27
31
|
Rewrite::With.with(sexp_processors, &body)
|
28
32
|
end
|
29
33
|
|
34
|
+
def expand(*sexp_processors, &body)
|
35
|
+
Rewrite::With.expand(sexp_processors, &body)
|
36
|
+
end
|
37
|
+
|
30
38
|
end
|
31
39
|
|
32
40
|
module InstanceMethods
|
data/lib/rewrite.rb
CHANGED
data/test/test_call_by_name.rb
CHANGED
@@ -2,8 +2,19 @@ require File.dirname(__FILE__) + '/test_helper.rb'
|
|
2
2
|
|
3
3
|
class TestCalledByName < Test::Unit::TestCase
|
4
4
|
|
5
|
+
include Rewrite::With
|
5
6
|
include Rewrite::Prelude
|
6
7
|
|
8
|
+
def test_andand_by_name
|
9
|
+
called_by_name(:our_and) { |x,y|
|
10
|
+
if temp = x
|
11
|
+
y
|
12
|
+
else
|
13
|
+
temp
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
7
18
|
def test_nullo_case
|
8
19
|
assert_equal(
|
9
20
|
Rewrite.arr_for do
|
@@ -21,6 +32,40 @@ class TestCalledByName < Test::Unit::TestCase
|
|
21
32
|
)
|
22
33
|
end
|
23
34
|
|
35
|
+
def test_regular_rewrite
|
36
|
+
assert_equal(
|
37
|
+
Rewrite.arr_for do
|
38
|
+
lambda { |foo|
|
39
|
+
foo.call(lambda { 2 }, lambda { 2 })
|
40
|
+
}.call(
|
41
|
+
lambda { |x, y| x.call + y.call }
|
42
|
+
)
|
43
|
+
end,
|
44
|
+
called_by_name(:foo) { |x, y| x + y }.process(
|
45
|
+
Rewrite.sexp_for do
|
46
|
+
foo(2,2)
|
47
|
+
end
|
48
|
+
).to_a
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_splatted_rewrite
|
53
|
+
assert_equal(
|
54
|
+
Rewrite.arr_for do
|
55
|
+
lambda { |foo|
|
56
|
+
foo.call(Rewrite::CallSplattedByThunk::Parameters.new(lambda { 2 }, lambda { 2 }))
|
57
|
+
}.call(
|
58
|
+
lambda { |*args| args.inject { |acc, item| acc + item } }
|
59
|
+
)
|
60
|
+
end,
|
61
|
+
called_by_name(:foo) { |*args| args.inject { |acc, item| acc + item } }.process(
|
62
|
+
Rewrite.sexp_for do
|
63
|
+
foo(2,2)
|
64
|
+
end
|
65
|
+
).to_a
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
24
69
|
def test_thunk_semantics
|
25
70
|
Rewrite.with(
|
26
71
|
called_by_name(:foo) { |change_it, get_it|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'parse_tree'
|
4
|
+
require 'sexp_processor'
|
5
|
+
|
6
|
+
module Rewrite
|
7
|
+
|
8
|
+
class TestWith < Test::Unit::TestCase
|
9
|
+
|
10
|
+
include Rewrite::With
|
11
|
+
include Rewrite::Prelude
|
12
|
+
|
13
|
+
def test_rewrite_splatted
|
14
|
+
assert_equal(
|
15
|
+
lambda { |foo|
|
16
|
+
Rewrite.arr_for {
|
17
|
+
foo.call(Rewrite::CallSplattedByThunk::Parameters.new(lambda { :bar }))
|
18
|
+
}
|
19
|
+
}.call(nil),
|
20
|
+
CallSplattedByThunk.new(:foo).process(
|
21
|
+
Rewrite.sexp_for { foo(:bar) }
|
22
|
+
).to_a
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def raise_this(ex)
|
27
|
+
raise ex
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_rewrite_splatted_semantics
|
31
|
+
with(
|
32
|
+
called_by_name(:try_these) { |*clauses|
|
33
|
+
clauses.each { |clause| return clause rescue nil }
|
34
|
+
nil
|
35
|
+
}
|
36
|
+
) do
|
37
|
+
assert_nothing_raised(Exception) do
|
38
|
+
try_these(raise_this("foo"), raise_this("bar"), :foo)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -2,17 +2,6 @@ require File.dirname(__FILE__) + '/test_helper.rb'
|
|
2
2
|
|
3
3
|
class TestRewriteHelpers < Test::Unit::TestCase
|
4
4
|
|
5
|
-
def test_splat_variables
|
6
|
-
|
7
|
-
assert_equal(
|
8
|
-
lambda { |*a| a[0].call }.to_sexp.to_a,
|
9
|
-
Rewrite::RewriteVariablesAsThunkCalls.new(:a).process(
|
10
|
-
lambda { |*a| a[0] }.to_sexp
|
11
|
-
).to_a
|
12
|
-
)
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
5
|
def test_simple_rewrite
|
17
6
|
|
18
7
|
assert_equal(
|
data/website/index.html
CHANGED
@@ -33,7 +33,7 @@
|
|
33
33
|
<h1>rewrite</h1>
|
34
34
|
<div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/rewrite"; return false'>
|
35
35
|
<p>Get Version</p>
|
36
|
-
<a href="http://rubyforge.org/projects/rewrite" class="numbers">0.0
|
36
|
+
<a href="http://rubyforge.org/projects/rewrite" class="numbers">0.1.0</a>
|
37
37
|
</div>
|
38
38
|
<h1>→ ‘rewrite’</h1>
|
39
39
|
|
@@ -104,6 +104,12 @@
|
|
104
104
|
<p>Rewrite restricts things like andand or try to your code and your code alone. Sure, if you introduce a bug in your code, you may break things that directly depend on your code. But if you introduce “try” using rewrite instead of modifying Object, you will not reach out across your project and break something entirely unrelated that happens to have defined its own version of try in a completely different way.</p>
|
105
105
|
|
106
106
|
|
107
|
+
<h2>New! called_by_name</h2>
|
108
|
+
|
109
|
+
|
110
|
+
<p>See <a href="http://weblog.raganwald.com/2008/06/macros-hygiene-and-call-by-name-in-ruby.html">Macros, Hygiene, and Call By Name in Ruby</a> for details, more docs to come presently…</p>
|
111
|
+
|
112
|
+
|
107
113
|
<h2>How does it work?</h2>
|
108
114
|
|
109
115
|
|
@@ -226,7 +232,7 @@ rake install_gem</pre>
|
|
226
232
|
|
227
233
|
<p>Comments are welcome. Send an email to <a href="mailto:raganwald+rewrite@gmail.com">Reg Braithwaite</a> email via the <a href="http://groups.google.com/group/rewrite">forum</a></p>
|
228
234
|
<p class="coda">
|
229
|
-
<a href="http://weblog.raganwald.com/">Reginald Braithwaite</a>,
|
235
|
+
<a href="http://weblog.raganwald.com/">Reginald Braithwaite</a>, 23rd June 2008<br>
|
230
236
|
Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
|
231
237
|
</p>
|
232
238
|
</div>
|
data/website/index.txt
CHANGED
@@ -55,6 +55,10 @@ Also, imagine if you introduce try and are careful not to break anything. Now so
|
|
55
55
|
|
56
56
|
Rewrite restricts things like andand or try to your code and your code alone. Sure, if you introduce a bug in your code, you may break things that directly depend on your code. But if you introduce “try” using rewrite instead of modifying Object, you will not reach out across your project and break something entirely unrelated that happens to have defined its own version of try in a completely different way.
|
57
57
|
|
58
|
+
h2. New! called_by_name
|
59
|
+
|
60
|
+
See "Macros, Hygiene, and Call By Name in Ruby":http://weblog.raganwald.com/2008/06/macros-hygiene-and-call-by-name-in-ruby.html for details, more docs to come presently...
|
61
|
+
|
58
62
|
h2. How does it work?
|
59
63
|
|
60
64
|
Rewrite takes your code, converts it to an sexp with Parse Tree, then rewrites the sexp using one or more rewriters you specify. Finally, it converts the sexp back to Ruby with Ruby2Ruby and evals it. It does this when the code is first read, not every time it is invoked, so we mitigate the “do not use andand in a tight loop” problem.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rewrite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reg Braithwaite
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-06-
|
12
|
+
date: 2008-06-23 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -97,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
97
|
requirements: []
|
98
98
|
|
99
99
|
rubyforge_project: rewrite
|
100
|
-
rubygems_version: 1.
|
100
|
+
rubygems_version: 1.2.0
|
101
101
|
signing_key:
|
102
102
|
specification_version: 2
|
103
103
|
summary: Syntactic metaprogramming for Ruby
|
@@ -105,6 +105,7 @@ test_files:
|
|
105
105
|
- test/test_andand.rb
|
106
106
|
- test/test_call_by_name.rb
|
107
107
|
- test/test_call_by_thunk.rb
|
108
|
+
- test/test_call_splatted_by_name.rb
|
108
109
|
- test/test_helper.rb
|
109
110
|
- test/test_rewriter_helpers.rb
|
110
111
|
- test/test_syntax_let.rb
|