metamorpher 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +2 -0
- data/README.md +46 -2
- data/RELEASES.md +4 -1
- data/lib/metamorpher/builders/ast/builder.rb +7 -0
- data/lib/metamorpher/builders/ast/literal_builder.rb +1 -1
- data/lib/metamorpher/builders/ast/term_set_builder.rb +20 -0
- data/lib/metamorpher/builders/ruby/builder.rb +4 -6
- data/lib/metamorpher/matcher/matching.rb +9 -0
- data/lib/metamorpher/refactorer.rb +0 -6
- data/lib/metamorpher/rewriter/substitution.rb +6 -0
- data/lib/metamorpher/terms/term.rb +4 -0
- data/lib/metamorpher/terms/term_set.rb +17 -0
- data/lib/metamorpher/transformer/base.rb +8 -8
- data/lib/metamorpher/version.rb +1 -1
- data/spec/integration/ast/builder_spec.rb +2 -0
- data/spec/integration/ast/matcher_spec.rb +60 -0
- data/spec/integration/ast/rewriter_spec.rb +56 -0
- data/spec/integration/ruby/builder_spec.rb +18 -0
- data/spec/integration/ruby/mutator_spec.rb +11 -11
- data/spec/support/shared_examples/shared_examples_for_term_set_builder.rb +39 -0
- data/spec/unit/builders/ast/term_set_builder_spec.rb +5 -0
- data/spec/unit/matcher/matching_spec.rb +19 -0
- data/spec/unit/rewriter/substitution_spec.rb +20 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8979355b8d41bbbc8c4f0a547f68e92cef86b17
|
4
|
+
data.tar.gz: bc38d9940581f4172e381617cd127d8e2e862994
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f78c4e7669975915d24d7a75bcdfb1481745a01c8fbad1f0264137d55b5630d1e87173000cb86f3c2f70869dfdb5ad880a65dc5cefeea1db1d5d6895ee9ff84
|
7
|
+
data.tar.gz: da8af5b60c5f6ce8ce759838668d57127b29e9f1277f99ca90c11f45ed6169517bc6853b1f5e47d3d74958c2c112aefa8a747507f36056e69d30ad99f17e29bd
|
data/.codeclimate.yml
ADDED
data/README.md
CHANGED
@@ -53,6 +53,7 @@ The primary data structure used for [rewriting](#rewriters) and for [matching](#
|
|
53
53
|
* Variable - a named node, which is bound to a subterm (subtree) during [matching](#matchers).
|
54
54
|
* Greedy variable - a variable that is bound to a set of subterms during [matching](#matchers).
|
55
55
|
* Derivation - a placeholder node, which is replaced during [rewriting](#rewriters).
|
56
|
+
* Term Set - a collection of terms (potentially of mixed types).
|
56
57
|
|
57
58
|
To simplify the construction of terms, metamorpher provides the `Metamorpher::Builders::AST::Builder` class, which is demonstrated below.
|
58
59
|
|
@@ -76,6 +77,9 @@ builder.derivation! :key, :value do |key, value, builder|
|
|
76
77
|
builder.pair(key, value)
|
77
78
|
end
|
78
79
|
# [KEY, VALUE] -> ...
|
80
|
+
|
81
|
+
builder.either! builder.literal!(:succ), builder.variable!(:n)
|
82
|
+
# TermSet[succ, N]
|
79
83
|
```
|
80
84
|
|
81
85
|
Variables can be conditional, in which case they are specified by passing a block:
|
@@ -104,11 +108,12 @@ builder.PAIRS_ { |literal| literal.name =~ /^find_by_/ } #=> PAIRS+?
|
|
104
108
|
|
105
109
|
#### Coercion of non-terms to literals
|
106
110
|
|
107
|
-
When constructing a literal, the builder ensures that any children are converted to literals if they are not already a term:
|
111
|
+
When constructing a literal or a term set, the builder ensures that any children are converted to literals if they are not already a term:
|
108
112
|
|
109
113
|
```ruby
|
110
114
|
builder.literal!(:add, :x, :y) # => add(x, y)
|
111
115
|
builder.add(:x, :y) # => add(x, y)
|
116
|
+
builder.either!(:add, :subtract) # => TermSet[add, subtract]
|
112
117
|
```
|
113
118
|
|
114
119
|
Without automatic coercion, the statements above would be written as follows. Note that they are more verbose:
|
@@ -116,6 +121,7 @@ Without automatic coercion, the statements above would be written as follows. No
|
|
116
121
|
```ruby
|
117
122
|
builder.literal!(:add, builder.literal!(:x), builder.literal!(:y)) # => add(x, y)
|
118
123
|
builder.add(builder.x, builder.y) # => add(x, y)
|
124
|
+
builder.either!(builder.add, builder.subtract) # => TermSet[add, subtract]
|
119
125
|
```
|
120
126
|
|
121
127
|
Note that coercion isn't necessary (and isn't applied) when the children of a literal are already terms:
|
@@ -123,6 +129,7 @@ Note that coercion isn't necessary (and isn't applied) when the children of a li
|
|
123
129
|
```ruby
|
124
130
|
builder.literal!(:add, builder.variable!(:n), builder.variable!(:m)) # => add(N, M)
|
125
131
|
builder.add(builder.N, builder.M) # => add(N, M)
|
132
|
+
builder.either!(builder.N, builder.M) # => TermSet[N, M]
|
126
133
|
```
|
127
134
|
|
128
135
|
### Matchers
|
@@ -134,6 +141,9 @@ Metamorpher provides the `Metamorpher::Matcher` module for specifying matchers.
|
|
134
141
|
```ruby
|
135
142
|
require "metamorpher"
|
136
143
|
|
144
|
+
# Use the AST builder
|
145
|
+
Metamorpher.configure(:ast)
|
146
|
+
|
137
147
|
class SuccZeroMatcher
|
138
148
|
include Metamorpher::Matcher
|
139
149
|
include Metamorpher::Builders::AST
|
@@ -154,6 +164,33 @@ result = SuccZeroMatcher.new.run(expression)
|
|
154
164
|
result.matches? # => false
|
155
165
|
```
|
156
166
|
|
167
|
+
#### Alternatives
|
168
|
+
|
169
|
+
Matching can search for several expressions to match at a time. Metamorpher provides TermSets for this purpose. Recall that TermSets are built using `builder.either!`
|
170
|
+
|
171
|
+
For example, we can extend our previous matcher to search for the expressions `succ(0)` and `pred(2)` at the same time.
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
class VerboseOneMatcher
|
175
|
+
include Metamorpher::Matcher
|
176
|
+
include Metamorpher::Builders::AST
|
177
|
+
|
178
|
+
def pattern
|
179
|
+
builder.either!(builder.succ(0), builder.pred(2))
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
expression = Metamorpher.builder.succ(0) # => succ(0)
|
184
|
+
result = VerboseOneMatcher.new.run(expression)
|
185
|
+
# => <Metamorpher::Matcher::Match root=succ(0), substitution={}>
|
186
|
+
result.matches? # => true
|
187
|
+
|
188
|
+
expression = Metamorpher.builder.pred(2) # => pred(2)
|
189
|
+
result = VerboseOneMatcher.new.run(expression)
|
190
|
+
# => <Metamorpher::Matcher::Match root=pred(2), substitution={}>
|
191
|
+
result.matches? # => true
|
192
|
+
```
|
193
|
+
|
157
194
|
#### Variables
|
158
195
|
|
159
196
|
Matching is more powerful when we can allow for some variability in the expressions that we wish to match. Metamorpher provides variables for this purpose.
|
@@ -352,6 +389,7 @@ Recall that term is a tree (i.e., an acyclic graph), whose nodes are either a:
|
|
352
389
|
* Variable - a named node, which is bound to a subterm (subtree) during [matching](#matchers).
|
353
390
|
* Greedy variable - a variable that is bound to a set of subterms during [matching](#matchers).
|
354
391
|
* Derivation - a placeholder node, which is replaced during [rewriting](#rewriters).
|
392
|
+
* Term Set - a collection of terms (potentially of mixed types).
|
355
393
|
|
356
394
|
The following examples demonstrate the way in which terms can built from strings that resemble Ruby programs:
|
357
395
|
|
@@ -398,6 +436,12 @@ builder
|
|
398
436
|
# [KEY, VALUE] -> ...
|
399
437
|
```
|
400
438
|
|
439
|
+
To build a term sets, provide several arguments:
|
440
|
+
|
441
|
+
```ruby
|
442
|
+
builder.build("true", "false") # => TermSet[true, false]
|
443
|
+
```
|
444
|
+
|
401
445
|
### Transformers
|
402
446
|
|
403
447
|
Transformers are [rewriters](#rewriters) that are specialised for rewriting program source code. A transformer parses a program's source code, rewrites the source code, and returns the unparsed, rewritten source code.
|
@@ -452,7 +496,7 @@ class LessThanMutator
|
|
452
496
|
end
|
453
497
|
|
454
498
|
def replacements
|
455
|
-
builder.
|
499
|
+
builder.build("A > B", "A == B")
|
456
500
|
end
|
457
501
|
end
|
458
502
|
|
data/RELEASES.md
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
## v0.2.1 (12 May 2015)
|
4
|
+
* Provide support for term sets, which make it possible to match (or rewrite to) multiple expressions at once
|
5
|
+
|
3
6
|
## v0.2.0 (9 May 2015)
|
4
|
-
* Provide support for mutators which are similar to refactorers but produce multiple transformed programs
|
7
|
+
* Provide support for mutators, which are similar to refactorers but produce multiple transformed programs
|
5
8
|
|
6
9
|
## v0.1.1 (8 May 2015)
|
7
10
|
* Update dependencies
|
@@ -1,7 +1,9 @@
|
|
1
|
+
require "metamorpher/terms/term_set"
|
1
2
|
require "metamorpher/builders/ast/literal_builder"
|
2
3
|
require "metamorpher/builders/ast/variable_builder"
|
3
4
|
require "metamorpher/builders/ast/greedy_variable_builder"
|
4
5
|
require "metamorpher/builders/ast/derivation_builder"
|
6
|
+
require "metamorpher/builders/ast/term_set_builder"
|
5
7
|
require "forwardable"
|
6
8
|
|
7
9
|
module Metamorpher
|
@@ -13,6 +15,7 @@ module Metamorpher
|
|
13
15
|
def_delegator :variable_builder, :variable!
|
14
16
|
def_delegator :greedy_variable_builder, :greedy_variable!
|
15
17
|
def_delegator :derivation_builder, :derivation!
|
18
|
+
def_delegator :term_set_builder, :either!
|
16
19
|
|
17
20
|
def method_missing(method, *arguments, &block)
|
18
21
|
builders_with_shorthand
|
@@ -45,6 +48,10 @@ module Metamorpher
|
|
45
48
|
def derivation_builder
|
46
49
|
@derivation_builder ||= DerivationBuilder.new
|
47
50
|
end
|
51
|
+
|
52
|
+
def term_set_builder
|
53
|
+
@term_set_builder ||= TermSetBuilder.new
|
54
|
+
end
|
48
55
|
end
|
49
56
|
end
|
50
57
|
end
|
@@ -5,7 +5,7 @@ module Metamorpher
|
|
5
5
|
module AST
|
6
6
|
class LiteralBuilder
|
7
7
|
def literal!(name, *children)
|
8
|
-
Terms::Literal.new(name: name, children: children.map
|
8
|
+
Terms::Literal.new(name: name, children: children.map(&method(:termify)))
|
9
9
|
end
|
10
10
|
|
11
11
|
def shorthand?(method, *_arguments, &_block)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "metamorpher/terms/term_set"
|
2
|
+
require "metamorpher/terms/literal"
|
3
|
+
|
4
|
+
module Metamorpher
|
5
|
+
module Builders
|
6
|
+
module AST
|
7
|
+
class TermSetBuilder
|
8
|
+
def either!(*terms)
|
9
|
+
Terms::TermSet.new(terms: terms.map(&method(:termify)))
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def termify(item)
|
15
|
+
item.is_a?(Terms::Term) ? item : Terms::Literal.new(name: item)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -2,17 +2,15 @@ require "metamorpher/drivers/ruby"
|
|
2
2
|
require "metamorpher/builders/ruby/term"
|
3
3
|
require "metamorpher/builders/ruby/uppercase_constant_rewriter"
|
4
4
|
require "metamorpher/builders/ruby/uppercase_rewriter"
|
5
|
+
require "metamorpher/terms/term_set"
|
5
6
|
|
6
7
|
module Metamorpher
|
7
8
|
module Builders
|
8
9
|
module Ruby
|
9
10
|
class Builder
|
10
|
-
def build(
|
11
|
-
decorate(rewrite(parse(source)))
|
12
|
-
|
13
|
-
|
14
|
-
def build_all(*sources)
|
15
|
-
sources.map(&method(:build))
|
11
|
+
def build(*sources)
|
12
|
+
terms = sources.map { |source| decorate(rewrite(parse(source))) }
|
13
|
+
terms.size == 1 ? terms.first : Metamorpher::Terms::TermSet.new(terms: terms)
|
16
14
|
end
|
17
15
|
|
18
16
|
private
|
@@ -41,6 +41,15 @@ module Metamorpher
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
def visit_termset(termset)
|
45
|
+
matches = termset.terms.map { |term| term.match(other) }.select(&:matches?)
|
46
|
+
if matches.any?
|
47
|
+
matches.first
|
48
|
+
else
|
49
|
+
Matcher::NoMatch.new
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
44
53
|
def visit_derived(_derived)
|
45
54
|
fail MatchingError, "Cannot match against a derived variable."
|
46
55
|
end
|
@@ -32,19 +32,19 @@ module Metamorpher
|
|
32
32
|
|
33
33
|
def reduce_to_replacements(src, literal)
|
34
34
|
[].tap do |replacements|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
replacements << Site.new(original_position, original_code,
|
35
|
+
rule.reduce(literal) do |original, rewritings|
|
36
|
+
original_position = driver.source_location_for(original)
|
37
|
+
original_code = src[original_position]
|
38
|
+
|
39
|
+
rewritings.alternatives.each do |rewriting|
|
40
|
+
replacements << Site.new(original_position, original_code, driver.unparse(rewriting))
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
47
|
-
@
|
46
|
+
def rule
|
47
|
+
@rule ||= Rewriter::Rule.new(pattern: pattern, replacement: replacement)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
data/lib/metamorpher/version.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "metamorpher"
|
2
|
+
require "metamorpher/terms/term_set"
|
2
3
|
|
3
4
|
describe Metamorpher do
|
4
5
|
subject { Metamorpher.builder }
|
@@ -10,4 +11,5 @@ describe Metamorpher do
|
|
10
11
|
it_behaves_like "a variable builder"
|
11
12
|
it_behaves_like "a greedy variable builder"
|
12
13
|
it_behaves_like "a derivation builder"
|
14
|
+
it_behaves_like "a term set builder"
|
13
15
|
end
|
@@ -129,4 +129,64 @@ describe "Matching" do
|
|
129
129
|
expect(subject.run(expression)).not_to have_matched
|
130
130
|
end
|
131
131
|
end
|
132
|
+
|
133
|
+
describe "alternatives" do
|
134
|
+
class CalculatorOperatorMatcher
|
135
|
+
include Metamorpher::Matcher
|
136
|
+
include Metamorpher::Builders::AST
|
137
|
+
|
138
|
+
def pattern
|
139
|
+
builder.either!(
|
140
|
+
builder.add(builder.ARGS_),
|
141
|
+
builder.subtract(builder.ARGS_),
|
142
|
+
builder.clear
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
subject { CalculatorOperatorMatcher.new }
|
148
|
+
|
149
|
+
it "should return a match for matching add expressions" do
|
150
|
+
expressions = [
|
151
|
+
builder.add(1),
|
152
|
+
builder.add(1, 2),
|
153
|
+
builder.add(1, 2, 3),
|
154
|
+
builder.add(1, builder.succ(:n), 2)
|
155
|
+
]
|
156
|
+
|
157
|
+
expressions.each do |expression|
|
158
|
+
expect(subject.run(expression)).to have_matched(expression)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should return a match for matching subtract expressions" do
|
163
|
+
expressions = [
|
164
|
+
builder.subtract(1),
|
165
|
+
builder.subtract(1, 2),
|
166
|
+
builder.subtract(1, 2, 3),
|
167
|
+
builder.subtract(1, builder.succ(:n), 2)
|
168
|
+
]
|
169
|
+
|
170
|
+
expressions.each do |expression|
|
171
|
+
expect(subject.run(expression)).to have_matched(expression)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should return a match for matching clear expression" do
|
176
|
+
expect(subject.run(builder.clear)).to have_matched(builder.clear)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should return no match for near misses" do
|
180
|
+
expressions = [
|
181
|
+
builder.add,
|
182
|
+
builder.subtract,
|
183
|
+
builder.empty,
|
184
|
+
builder.multiply(1, 2, 3)
|
185
|
+
]
|
186
|
+
|
187
|
+
expressions.each do |expression|
|
188
|
+
expect(subject.run(expression)).not_to have_matched(expression)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
132
192
|
end
|
@@ -82,6 +82,33 @@ describe "Rewriting" do
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
+
describe "multiple rewritings via termset" do
|
86
|
+
class FlexiblePluraliseRewriter
|
87
|
+
include Metamorpher::Rewriter
|
88
|
+
include Metamorpher::Builders::AST
|
89
|
+
|
90
|
+
def pattern
|
91
|
+
builder.SINGULAR
|
92
|
+
end
|
93
|
+
|
94
|
+
def replacement
|
95
|
+
builder.either!(
|
96
|
+
builder.derivation!(:singular) { |singular| builder.literal!(singular.name + "s") },
|
97
|
+
builder.derivation!(:singular) { |singular| builder.literal!(singular.name + "es") }
|
98
|
+
)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
subject { FlexiblePluraliseRewriter.new }
|
103
|
+
|
104
|
+
it "should rewrite using each derivation" do
|
105
|
+
expression = builder.literal! "virus"
|
106
|
+
reduced = builder.either!(builder.literal!("viruss"), builder.literal!("viruses"))
|
107
|
+
|
108
|
+
expect(subject.reduce(expression)).to eq(reduced)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
85
112
|
describe "derivations" do
|
86
113
|
describe "from a single variable" do
|
87
114
|
class PluraliseRewriter
|
@@ -134,5 +161,34 @@ describe "Rewriting" do
|
|
134
161
|
expect(subject.reduce(expression)).to eq(reduced)
|
135
162
|
end
|
136
163
|
end
|
164
|
+
|
165
|
+
describe "to several alternatives" do
|
166
|
+
class FlexiblePluraliseRewriterInner
|
167
|
+
include Metamorpher::Rewriter
|
168
|
+
include Metamorpher::Builders::AST
|
169
|
+
|
170
|
+
def pattern
|
171
|
+
builder.SINGULAR
|
172
|
+
end
|
173
|
+
|
174
|
+
def replacement
|
175
|
+
builder.derivation!(:singular) do |singular|
|
176
|
+
builder.either!(
|
177
|
+
builder.literal!(singular.name + "s"),
|
178
|
+
builder.literal!(singular.name + "es")
|
179
|
+
)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
subject { FlexiblePluraliseRewriterInner.new }
|
185
|
+
|
186
|
+
it "should rewrite using each derivation" do
|
187
|
+
expression = builder.literal! "virus"
|
188
|
+
reduced = builder.either!(builder.literal!("viruss"), builder.literal!("viruses"))
|
189
|
+
|
190
|
+
expect(subject.reduce(expression)).to eq(reduced)
|
191
|
+
end
|
192
|
+
end
|
137
193
|
end
|
138
194
|
end
|
@@ -122,4 +122,22 @@ describe Metamorpher.builder do
|
|
122
122
|
expect(last_derived.base).to eq([:last])
|
123
123
|
end
|
124
124
|
end
|
125
|
+
|
126
|
+
describe "when building with alternatives" do
|
127
|
+
it "should produce a termset" do
|
128
|
+
actual = subject.build("1 + 1", "LEFT + RIGHT")
|
129
|
+
|
130
|
+
expected_literals = ast_builder.literal!(:send, ast_builder.int(1), :+, ast_builder.int(1))
|
131
|
+
expected_variables = ast_builder.literal!(:send, ast_builder.LEFT, :+, ast_builder.RIGHT)
|
132
|
+
expected = ast_builder.either!(expected_literals, expected_variables)
|
133
|
+
|
134
|
+
expect(actual).to eq(expected)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should raise for invalid source" do
|
138
|
+
silence_stream(STDERR) do
|
139
|
+
expect { subject.build("1 + ") }.to raise_error(Metamorpher::Drivers::ParseError)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
125
143
|
end
|
@@ -11,8 +11,8 @@ describe "Mutator" do
|
|
11
11
|
builder.build("A < B")
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
builder.
|
14
|
+
def replacement
|
15
|
+
builder.build("A > B", "A == B")
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -33,13 +33,13 @@ describe "Mutator" do
|
|
33
33
|
"end",
|
34
34
|
|
35
35
|
"def compare\n" \
|
36
|
-
" foo
|
37
|
-
" bar
|
36
|
+
" foo == bar\n" \
|
37
|
+
" bar < baz\n" \
|
38
38
|
"end",
|
39
39
|
|
40
40
|
"def compare\n" \
|
41
|
-
" foo
|
42
|
-
" bar
|
41
|
+
" foo < bar\n" \
|
42
|
+
" bar > baz\n" \
|
43
43
|
"end",
|
44
44
|
|
45
45
|
"def compare\n" \
|
@@ -52,7 +52,7 @@ describe "Mutator" do
|
|
52
52
|
let(:not_mutatable) { "foo == bar" }
|
53
53
|
|
54
54
|
describe "by calling mutate" do
|
55
|
-
describe "for code that can be mutated"do
|
55
|
+
describe "for code that can be mutated" do
|
56
56
|
it "should return the mutated code" do
|
57
57
|
expect(subject.mutate(mutatable)).to eq(mutated)
|
58
58
|
end
|
@@ -60,8 +60,8 @@ describe "Mutator" do
|
|
60
60
|
it "should yield for each mutation site" do
|
61
61
|
expect { |b| subject.mutate(mutatable, &b) }.to yield_successive_args(
|
62
62
|
site_for(14..22, "foo < bar", "foo > bar"),
|
63
|
-
site_for(26..34, "bar < baz", "bar > baz"),
|
64
63
|
site_for(14..22, "foo < bar", "foo == bar"),
|
64
|
+
site_for(26..34, "bar < baz", "bar > baz"),
|
65
65
|
site_for(26..34, "bar < baz", "bar == baz")
|
66
66
|
)
|
67
67
|
end
|
@@ -89,8 +89,8 @@ describe "Mutator" do
|
|
89
89
|
it "should yield for each mutating site" do
|
90
90
|
expect { |b| subject.mutate_file(mutatable_file, &b) }.to yield_successive_args(
|
91
91
|
site_for(14..22, "foo < bar", "foo > bar"),
|
92
|
-
site_for(26..34, "bar < baz", "bar > baz"),
|
93
92
|
site_for(14..22, "foo < bar", "foo == bar"),
|
93
|
+
site_for(26..34, "bar < baz", "bar > baz"),
|
94
94
|
site_for(26..34, "bar < baz", "bar == baz")
|
95
95
|
)
|
96
96
|
end
|
@@ -155,8 +155,8 @@ describe "Mutator" do
|
|
155
155
|
mutated,
|
156
156
|
[
|
157
157
|
site_for(14..22, "foo < bar", "foo > bar"),
|
158
|
-
site_for(26..34, "bar < baz", "bar > baz"),
|
159
158
|
site_for(14..22, "foo < bar", "foo == bar"),
|
159
|
+
site_for(26..34, "bar < baz", "bar > baz"),
|
160
160
|
site_for(26..34, "bar < baz", "bar == baz")
|
161
161
|
]
|
162
162
|
]
|
@@ -166,8 +166,8 @@ describe "Mutator" do
|
|
166
166
|
mutated,
|
167
167
|
[
|
168
168
|
site_for(14..22, "foo < bar", "foo > bar"),
|
169
|
-
site_for(26..34, "bar < baz", "bar > baz"),
|
170
169
|
site_for(14..22, "foo < bar", "foo == bar"),
|
170
|
+
site_for(26..34, "bar < baz", "bar > baz"),
|
171
171
|
site_for(26..34, "bar < baz", "bar == baz")
|
172
172
|
]
|
173
173
|
]
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "metamorpher/terms/term_set"
|
2
|
+
require "metamorpher/terms/literal"
|
3
|
+
require "metamorpher/terms/variable"
|
4
|
+
|
5
|
+
module Metamorpher
|
6
|
+
module Terms
|
7
|
+
shared_examples_for "a term set builder" do
|
8
|
+
describe "either!" do
|
9
|
+
it "should create an instance of TermSet" do
|
10
|
+
actual = subject.either!(Literal.new(name: :a), Variable.new(name: :b))
|
11
|
+
expected = TermSet.new(terms: [Literal.new(name: :a), Variable.new(name: :b)])
|
12
|
+
|
13
|
+
expect(actual).to eq(expected)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return an empty TermSet when given no arguments" do
|
17
|
+
actual = subject.either!
|
18
|
+
expected = Metamorpher::Terms::TermSet.new
|
19
|
+
|
20
|
+
expect(actual).to eq(expected)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should automatically convert arguments to literals" do
|
24
|
+
actual = subject.either!(:add, :subtract)
|
25
|
+
expected = TermSet.new(terms: [Literal.new(name: :add), Literal.new(name: :subtract)])
|
26
|
+
|
27
|
+
expect(actual).to eq(expected)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not automatically convert arguments that are already terms" do
|
31
|
+
actual = subject.either!(:add, Variable.new(name: :operator))
|
32
|
+
expected = TermSet.new(terms: [Literal.new(name: :add), Variable.new(name: :operator)])
|
33
|
+
|
34
|
+
expect(actual).to eq(expected)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -217,6 +217,25 @@ module Metamorpher
|
|
217
217
|
end
|
218
218
|
end
|
219
219
|
|
220
|
+
describe TermSet do
|
221
|
+
let(:first) { Literal.new(name: :first) }
|
222
|
+
let(:second) { Literal.new(name: :second) }
|
223
|
+
|
224
|
+
subject { TermSet.new(terms: [first, second]) }
|
225
|
+
|
226
|
+
it "should match when a child matches" do
|
227
|
+
matchee = Literal.new(name: :second)
|
228
|
+
|
229
|
+
expect(subject.match(matchee)).to have_matched(matchee)
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should not match when no child matches" do
|
233
|
+
matchee = Literal.new(name: :third)
|
234
|
+
|
235
|
+
expect(subject.match(matchee)).not_to have_matched
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
220
239
|
describe Derived do
|
221
240
|
it "should raise" do
|
222
241
|
root = Derived.new
|
@@ -93,5 +93,25 @@ module Metamorpher
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
96
|
+
|
97
|
+
describe TermSet do
|
98
|
+
let(:first_child) { Variable.new(name: :type) }
|
99
|
+
|
100
|
+
let(:second_child) do
|
101
|
+
Derived.new(
|
102
|
+
base: [:type],
|
103
|
+
derivation: -> (type) { Literal.new(name: type.name.reverse) }
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
subject { TermSet.new(terms: [first_child, second_child]) }
|
108
|
+
|
109
|
+
it "should perform substitution on each child" do
|
110
|
+
substitution = { type: Literal.new(name: "sub") }
|
111
|
+
expected = TermSet.new(terms: [Literal.new(name: "sub"), Literal.new(name: "sub".reverse)])
|
112
|
+
|
113
|
+
expect(subject.substitute(substitution)).to eq(expected)
|
114
|
+
end
|
115
|
+
end
|
96
116
|
end
|
97
117
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metamorpher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Louis Rose
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: attributable
|
@@ -130,6 +130,7 @@ executables: []
|
|
130
130
|
extensions: []
|
131
131
|
extra_rdoc_files: []
|
132
132
|
files:
|
133
|
+
- ".codeclimate.yml"
|
133
134
|
- ".gitignore"
|
134
135
|
- ".rspec"
|
135
136
|
- ".rubocop.yml"
|
@@ -152,6 +153,7 @@ files:
|
|
152
153
|
- lib/metamorpher/builders/ast/derivation_builder.rb
|
153
154
|
- lib/metamorpher/builders/ast/greedy_variable_builder.rb
|
154
155
|
- lib/metamorpher/builders/ast/literal_builder.rb
|
156
|
+
- lib/metamorpher/builders/ast/term_set_builder.rb
|
155
157
|
- lib/metamorpher/builders/ast/variable_builder.rb
|
156
158
|
- lib/metamorpher/builders/ruby.rb
|
157
159
|
- lib/metamorpher/builders/ruby/builder.rb
|
@@ -178,6 +180,7 @@ files:
|
|
178
180
|
- lib/metamorpher/terms/derived.rb
|
179
181
|
- lib/metamorpher/terms/literal.rb
|
180
182
|
- lib/metamorpher/terms/term.rb
|
183
|
+
- lib/metamorpher/terms/term_set.rb
|
181
184
|
- lib/metamorpher/terms/variable.rb
|
182
185
|
- lib/metamorpher/transformer/base.rb
|
183
186
|
- lib/metamorpher/transformer/merger.rb
|
@@ -199,10 +202,12 @@ files:
|
|
199
202
|
- spec/support/shared_examples/shared_examples_for_derivation_builders.rb
|
200
203
|
- spec/support/shared_examples/shared_examples_for_greedy_variable_builders.rb
|
201
204
|
- spec/support/shared_examples/shared_examples_for_literal_builders.rb
|
205
|
+
- spec/support/shared_examples/shared_examples_for_term_set_builder.rb
|
202
206
|
- spec/support/shared_examples/shared_examples_for_variable_builders.rb
|
203
207
|
- spec/unit/builders/ast/derivation_builder_spec.rb
|
204
208
|
- spec/unit/builders/ast/greedy_variable_builder_spec.rb
|
205
209
|
- spec/unit/builders/ast/literal_builder_spec.rb
|
210
|
+
- spec/unit/builders/ast/term_set_builder_spec.rb
|
206
211
|
- spec/unit/builders/ast/variable_builder_spec.rb
|
207
212
|
- spec/unit/builders/ruby/variable_replacement_visitor_spec.rb
|
208
213
|
- spec/unit/drivers/ruby_spec.rb
|
@@ -255,10 +260,12 @@ test_files:
|
|
255
260
|
- spec/support/shared_examples/shared_examples_for_derivation_builders.rb
|
256
261
|
- spec/support/shared_examples/shared_examples_for_greedy_variable_builders.rb
|
257
262
|
- spec/support/shared_examples/shared_examples_for_literal_builders.rb
|
263
|
+
- spec/support/shared_examples/shared_examples_for_term_set_builder.rb
|
258
264
|
- spec/support/shared_examples/shared_examples_for_variable_builders.rb
|
259
265
|
- spec/unit/builders/ast/derivation_builder_spec.rb
|
260
266
|
- spec/unit/builders/ast/greedy_variable_builder_spec.rb
|
261
267
|
- spec/unit/builders/ast/literal_builder_spec.rb
|
268
|
+
- spec/unit/builders/ast/term_set_builder_spec.rb
|
262
269
|
- spec/unit/builders/ast/variable_builder_spec.rb
|
263
270
|
- spec/unit/builders/ruby/variable_replacement_visitor_spec.rb
|
264
271
|
- spec/unit/drivers/ruby_spec.rb
|