rewrite 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,221 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require File.expand_path(File.dirname(__FILE__) +'/union_of_entities_sequence.rb')
4
+ require File.expand_path(File.dirname(__FILE__) +'/symbol_entity.rb')
5
+ require File.expand_path(File.dirname(__FILE__) +'/sexp_entity.rb')
6
+ require File.expand_path(File.dirname(__FILE__) +'/returning.rb')
7
+
8
+ module Rewrite
9
+
10
+ module ByExample
11
+
12
+ class ObjectToMatcher
13
+
14
+ include Returning
15
+
16
+ attr_reader :binders, :bind_arguments
17
+
18
+ class << self
19
+ attr_accessor :debug
20
+ debug = false
21
+ end
22
+
23
+ def self.binding (*bind_arguments)
24
+ if bind_arguments.empty?
25
+ @base_object_to_sequence ||= self.new()
26
+ else
27
+ self.new(*bind_arguments)
28
+ end
29
+ end
30
+
31
+ def self.noisily
32
+ was = self.debug
33
+ begin
34
+ self.debug = true
35
+ yield
36
+ ensure
37
+ self.debug = was
38
+ end
39
+ end
40
+
41
+ def self.quietly
42
+ was = self.debug
43
+ begin
44
+ self.debug = false
45
+ yield
46
+ ensure
47
+ self.debug = was
48
+ end
49
+ end
50
+
51
+ def self.symbol_like_expression_matcher
52
+ @symbol_like_expression_matcher ||=
53
+ returning(
54
+ quietly do
55
+ self.from_object(
56
+ s(
57
+ UnionOfEntitiesSequence.new(:gvar, :dvar, :vcall, :lcall, :lit),
58
+ Bind.new(:variable_symbol, AnyEntity.new)
59
+ )
60
+ )
61
+ end
62
+ ) do |slem|
63
+ p slem.to_s if self.debug
64
+ end
65
+ end
66
+
67
+ def self.proc_capturer
68
+ @@proc_capturer ||= self.from_object(
69
+ s(:proc, nil, Bind.new(:sexp, AnyEntity.new))
70
+ )
71
+ end
72
+
73
+ def initialize (*bind_arguments)
74
+ @bind_arguments = bind_arguments
75
+ @binders = bind_arguments.map { |arg| bind_arg_to_binder(arg) }
76
+ end
77
+
78
+ def from_example(&proc)
79
+ if unfolded = self.class.proc_capturer.unfold(proc.to_sexp)
80
+ self.from_object(unfolded[:sexp])
81
+ end
82
+ end
83
+
84
+ def from_object o
85
+ matcher = convert(o)
86
+ raise "#{o.to_s} => #{matcher.to_s} does not describe a matcher" unless matcher.kind_of? EntityMatcher
87
+ matcher
88
+ end
89
+
90
+ def self.from_example(&proc)
91
+ (@o2s_from_object ||= self.new).from_example(&proc)
92
+ end
93
+
94
+ def self.from_object o
95
+ (@o2s_from_object ||= self.new).from_object(o)
96
+ end
97
+
98
+ def self.from_sexp(*elements)
99
+ from_object(elements)
100
+ end
101
+
102
+ protected
103
+
104
+ def convert(o)
105
+ p "***** #{o.to_s}" if self.class.debug
106
+ matcher = if o.kind_of? Sequence
107
+ o
108
+ elsif o.kind_of? EntityMatcher
109
+ o
110
+ elsif o.kind_of? Symbol
111
+ from_symbol(o)
112
+ elsif o.nil?
113
+ NilEntity.new
114
+ elsif o.kind_of? Array
115
+ p "#{o.inspect}.kind_of? Array" if self.class.debug
116
+ from_sexp(o)
117
+ else
118
+ raise "Don't know how to handle #{o.inspect}"
119
+ end
120
+ p "returning #{matcher.to_s} given #{o.to_s}" if self.class.debug
121
+ matcher
122
+ end
123
+
124
+ private
125
+
126
+ #--
127
+ #
128
+ # Changed this to avoid matching... but do we need it?
129
+ # When would we match this and not match sexp?
130
+ def from_symbol(sym)
131
+ self.binders.each do |binder|
132
+ if bound_object = binder.call(sym)
133
+ p "handling symbol #{sym.inspect} as bound match #{bound_object.to_s}" if self.class.debug
134
+ return bound_object
135
+ end
136
+ end
137
+ p "handling symbol #{sym.inspect} as itself" if self.class.debug
138
+ SymbolEntity.new(sym)
139
+ end
140
+
141
+ def from_sexp(sexp)
142
+ self.binders.each do |binder|
143
+ if bound_object = binder.call(sexp)
144
+ p "handling sexp #{sexp.to_s} as bound match #{sexp.to_s}" if self.class.debug
145
+ return bound_object
146
+ end
147
+ end
148
+ p "decomposing sexp #{sexp.to_s}" if self.class.debug
149
+ SexpEntity.for_sequence(
150
+ Composition.new(
151
+ *(sexp.map { |sub_sexp|
152
+ obj = ObjectToMatcher.binding(*bind_arguments).convert(sub_sexp)
153
+ if obj.kind_of? Sequence
154
+ obj
155
+ else
156
+ LengthOne.new(obj)
157
+ end
158
+ }
159
+ )
160
+ )
161
+ )
162
+ end
163
+
164
+ def bind_arg_to_binder(bind_arg)
165
+ p "trying to make a binder for #{bind_arg.inspect}" if self.class.debug
166
+ if bind_arg.is_a?(Array) && bind_arg.length == 1 && pattern = object_to_pattern(bind_arg.first)
167
+ p "binding sequence pattern #{pattern} for #{bind_arg.inspect}" if self.class.debug
168
+ lambda { |sexp_or_symbol|
169
+ p "trying to match #{sexp_or_symbol} against #{pattern.source}" if self.class.debug
170
+ unfolded = ObjectToMatcher.symbol_like_expression_matcher.unfold(sexp_or_symbol)
171
+ p "unfolded is #{unfolded.inspect}" if self.class.debug
172
+ symbol = unfolded[:variable_symbol] if unfolded
173
+ p "symbol is #{symbol.inspect}" if self.class.debug
174
+ name = symbol.to_s[pattern,1] if symbol
175
+ p "name is #{name.inspect}" if self.class.debug
176
+ if name
177
+ BindSequence.new(name)
178
+ elsif sexp_or_symbol.is_a?(Symbol) && name = sexp_or_symbol.to_s[pattern,1]
179
+ BindSequence.new(name)
180
+ end
181
+ }
182
+ elsif pattern = object_to_pattern(bind_arg)
183
+ p "binding entity pattern #{pattern.source} to #{bind_arg.inspect}" if self.class.debug
184
+ lambda { |sexp_or_symbol|
185
+ p "trying to match #{sexp_or_symbol} against #{pattern.source}" if self.class.debug
186
+ unfolded = ObjectToMatcher.symbol_like_expression_matcher.unfold(sexp_or_symbol)
187
+ p "unfolded is #{unfolded.inspect}" if self.class.debug
188
+ symbol = unfolded[:variable_symbol] if unfolded
189
+ p "symbol is #{symbol.inspect}" if self.class.debug
190
+ name = symbol.to_s[pattern,1] if symbol
191
+ p "name is #{name.inspect}" if self.class.debug
192
+ if name
193
+ Bind.new(name, AnyEntity.new)
194
+ elsif sexp_or_symbol.is_a?(Symbol) && name = sexp_or_symbol.to_s[pattern,1]
195
+ Bind.new(name, AnyEntity.new)
196
+ end
197
+ }
198
+ else
199
+ raise "unable to make a binder for #{bind_arg.inspect}"
200
+ end
201
+ end
202
+
203
+ def self.object_to_pattern(pattern_string_or_symbol)
204
+ if pattern_string_or_symbol.is_a?(Regexp)
205
+ pattern_string_or_symbol
206
+ elsif pattern_string_or_symbol.is_a?(String) || pattern_string_or_symbol.is_a?(Symbol)
207
+ Regexp.new("^(#{pattern_string_or_symbol.to_s})$")
208
+ else
209
+ nil
210
+ end
211
+ end
212
+
213
+ def object_to_pattern(arg)
214
+ self.class.object_to_pattern(arg)
215
+ end
216
+
217
+ end
218
+
219
+ end
220
+
221
+ end
@@ -0,0 +1,29 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module Rewrite
4
+
5
+ module ByExample
6
+
7
+ module Returning
8
+ module ClassMethods
9
+ def returning(something)
10
+ yield something if block_given?
11
+ something
12
+ end
13
+ end
14
+
15
+ module InstanceMethods
16
+ def returning(something)
17
+ ClassMethods.returning(something)
18
+ end
19
+ end
20
+
21
+ def self.included(receiver)
22
+ receiver.extend ClassMethods
23
+ receiver.send :include, InstanceMethods
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,57 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module Rewrite
4
+
5
+ module ByExample
6
+
7
+ # Base class fo all classes that match one or more entities within a list.
8
+ #
9
+ # Includes some backtracking magic to handle cases isomorphic to /.*foo/
10
+ class Sequence
11
+
12
+ # unfolders takes a length and answers a (possible empty) Enumerable of lambdas,
13
+ # each of which has the method #call(array) and returning an unfold.
14
+ #
15
+ # Now obviously this means that there are three different ways to represent alternation.
16
+ # First, if a sequence matches two things of different lengths, like A | AA, it returns
17
+ # A for unfolders_by_length(1) and AA for unfolders_by_length(2). However, if a sequence
18
+ # matches two things of the same length, tehre are two different ways to represent that.
19
+ #
20
+ # For example, if a sequence matches A | B, it can do either of:
21
+ #
22
+ # unfolders_by_length(1) =>
23
+ # [ lambda { |arr| A =~ arr.first }, lambda { |arr| B =~ arr.first } ]
24
+ #
25
+ # or:
26
+ #
27
+ # unfolders_by_length(1) =>
28
+ # [ lambda { |arr| A =~ arr.first || B =~ arr.first } ]
29
+ #
30
+ # Note the difference: The first example returns two lambdas, one matching A and the
31
+ # other matching B. the second example returns a single lambda matching A or B.
32
+ #
33
+ # When would you use one over the other? The first example is to be used when you wish
34
+ # to support backtracking. The second example is to be used when you do not wish to
35
+ # support backtracking.
36
+ #
37
+ def unfolders_by_length(length)
38
+ raise 'implemented by subclass'
39
+ end
40
+ remove_method :unfolders_by_length
41
+
42
+ # Answer a range of possible lengths
43
+ def length_range
44
+ raise 'implemented by subclass'
45
+ end
46
+ remove_method :length_range
47
+
48
+ def fold(enum_of_bindings)
49
+ raise "Subclass #{self.class} should implement fold"
50
+ end
51
+ remove_method :fold
52
+
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,51 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module Rewrite
4
+
5
+ module ByExample
6
+
7
+ # A SexpEntity is a matcher that is initialized with example sexps,
8
+ # so it matches a literal tree. I think SexpEntity is right, however
9
+ # not all things you build with ObjectToSequence are Sexps?
10
+ class SexpEntity < EntityMatcher
11
+
12
+ attr_accessor :sequence
13
+
14
+ def initialize(*foo)
15
+ raise "refactor to ObjectToMatcher" unless foo.empty?
16
+ raise "refactor to ObjectToMatcher" if block_given?
17
+ end
18
+
19
+ def self.for_sequence(sequence)
20
+ s = SexpEntity.new
21
+ s.sequence = sequence
22
+ return s
23
+ end
24
+
25
+ def unfold (sexp)
26
+ if sexp.kind_of? Array
27
+ self.sequence.unfolders_by_length(sexp.length).each { |unfolder|
28
+ unfolded = unfolder.call(sexp)
29
+ return unfolded if unfolded && (predicate.nil? || predicate.call(unfolded))
30
+ }
31
+ nil
32
+ end
33
+ end
34
+
35
+ def fold (enum_of_bindings)
36
+ self.sequence.fold(enum_of_bindings)
37
+ end
38
+
39
+ def to_s
40
+ "s( #{sequence.to_s} )"
41
+ end
42
+
43
+ def self.proc_capturer
44
+ SexpEntity.new(:proc, nil, Bind.new(:sexp, AnyEntity.new))
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,37 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module Rewrite
4
+
5
+ module ByExample
6
+
7
+ # Matches a specific symbol: SymbolEntity.new(:foo) matches :foo.
8
+ #
9
+ # Note that this is not the same thing as matching the use of a symbol in Ruby
10
+ # code, because a literal symbol in Ruby is actually represented as:
11
+ #
12
+ # s(:lit, :foo)
13
+ class SymbolEntity < EntityMatcher
14
+
15
+ attr_accessor :symbol
16
+
17
+ def initialize(symbol)
18
+ self.symbol = symbol
19
+ end
20
+
21
+ def unfold (sexp)
22
+ {} if self.symbol == sexp
23
+ end
24
+
25
+ def fold (enum_of_bindings)
26
+ self.symbol
27
+ end
28
+
29
+ def to_s
30
+ ":#{self.symbol.to_s}"
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,57 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'rubygems'
4
+ require 'sexp_processor'
5
+
6
+ module Rewrite
7
+
8
+ module ByExample
9
+
10
+ #--
11
+ #
12
+ # TODO: 'mixed modes' such that we can write string_to_proc as an unhygienic
13
+ # TODO: composable unhygienics (entity matchers are already composable)
14
+ # TODO: how would we rename variable references safely with unhygienics?
15
+ # CSS model (heterogeneous patterns)? Combinatoral?
16
+ class Unhygienic < SexpProcessor
17
+
18
+ attr_accessor :from_unfolder, :to_folder, :bind_arguments
19
+
20
+ def from(*bind_arguments, &proc)
21
+ self.bind_arguments = bind_arguments
22
+ self.from_unfolder = ObjectToMatcher.binding(*self.bind_arguments).from_example(&proc)
23
+ self
24
+ end
25
+
26
+ def to(&proc)
27
+ self.to_folder = ObjectToMatcher.binding(*self.bind_arguments).from_example(&proc)
28
+ self
29
+ end
30
+
31
+ def self.from(*bind_arguments, &proc)
32
+ self.new.from(*bind_arguments, &proc)
33
+ end
34
+
35
+ def process(exp)
36
+ raise "Need a from unfolder" unless from_unfolder
37
+ raise "Need a to folder" unless to_folder
38
+ Rewrite.recursive_s(process_inner(exp))
39
+ end
40
+
41
+ def process_inner(exp)
42
+ if exp.is_a? Array
43
+ exp = exp.map { |sub_exp| process_inner(sub_exp) }
44
+ end
45
+ if unfolded = from_unfolder.unfold(exp)
46
+ if refolded = to_folder.fold(unfolded)
47
+ exp = refolded
48
+ end
49
+ end
50
+ exp
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ end