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 CHANGED
@@ -8,3 +8,5 @@
8
8
  * added Please to the Prelude
9
9
  * 4 tiny enhancement:
10
10
  * added Try to the Prelude
11
+ * 5 minor enhancement:
12
+ * called_by_name
@@ -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
- # CallByThunk.new(:foo, :bar)
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
- # It then converts expressions of the form foo(expr1, expr2, ..., exprn) into
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
- # Warning: Extreme Funk ahead:
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
- @let = Rewrite::DefVar.new(name, Rewrite::RewriteParametersAsThunkCalls.new.process(eval(proc.to_sexp.inspect)))
81
- @call_by_thunk = Rewrite::CallByThunk.new(name)
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)
@@ -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
@@ -1,8 +1,8 @@
1
1
  module Rewrite #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 0
5
- TINY = 6
4
+ MINOR = 1
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
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
- ruby = Ruby2Ruby.new.process(rewritten)
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
@@ -8,6 +8,10 @@ Dir["#{File.dirname(__FILE__)}/rewrite/*.rb"].each do |element|
8
8
  require element
9
9
  end
10
10
 
11
+ #--
12
+ #
13
+ # TODO: Hygienic Inline Methods
14
+ #
11
15
  module Rewrite
12
16
 
13
17
  module ClassMethods
@@ -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.6</a>
36
+ <a href="http://rubyforge.org/projects/rewrite" class="numbers">0.1.0</a>
37
37
  </div>
38
38
  <h1>&#x2192; &#8216;rewrite&#8217;</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&#8230;</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>, 18th June 2008<br>
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.6
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-18 00:00:00 -04:00
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.1.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