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.
- data/History.txt +2 -0
- data/Manifest.txt +19 -0
- data/lib/rewrite/by_example.rb +39 -0
- data/lib/rewrite/by_example/any_entity.rb +32 -0
- data/lib/rewrite/by_example/bind.rb +41 -0
- data/lib/rewrite/by_example/bind_sequence.rb +46 -0
- data/lib/rewrite/by_example/composition.rb +104 -0
- data/lib/rewrite/by_example/entity_matcher.rb +48 -0
- data/lib/rewrite/by_example/length_one.rb +50 -0
- data/lib/rewrite/by_example/literal_entity.rb +25 -0
- data/lib/rewrite/by_example/nil_entity.rb +26 -0
- data/lib/rewrite/by_example/object_to_matcher.rb +221 -0
- data/lib/rewrite/by_example/returning.rb +29 -0
- data/lib/rewrite/by_example/sequence.rb +57 -0
- data/lib/rewrite/by_example/sexp_entity.rb +51 -0
- data/lib/rewrite/by_example/symbol_entity.rb +37 -0
- data/lib/rewrite/by_example/unhygienic.rb +57 -0
- data/lib/rewrite/by_example/union_of_entities_sequence.rb +39 -0
- data/lib/rewrite/version.rb +1 -1
- data/test/test_by_example.rb +61 -37
- data/test/test_object_to_sequence_and_matcher.rb +128 -0
- data/website/index.html +4 -2
- data/website/index.txt +2 -0
- metadata +22 -2
@@ -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
|