rewrite 0.2.0 → 0.3.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.
@@ -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