rewrite 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|