metamorpher 0.2.0 → 0.2.1
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.
- 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
|