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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 07d706738851b486c6fc3a58140b5046a1569a6e
4
- data.tar.gz: 77e1d643b5816016c0d054954af1d9f7f9e6bbae
3
+ metadata.gz: d8979355b8d41bbbc8c4f0a547f68e92cef86b17
4
+ data.tar.gz: bc38d9940581f4172e381617cd127d8e2e862994
5
5
  SHA512:
6
- metadata.gz: 6a15ddbe30e40c36ec9d4ea427d6e5558650b00d6aabb907d2954cd5327abe5effc232355fc249fe0faa59191fe9546dadb2846ae4ef7e7f56796b52446ed3fe
7
- data.tar.gz: 063d0829bb4dbc7de52d3a50b78599d752d395a94f0b3e5cb9dd9067dfb3f8eae389ec6d3380db1a6b865f9b13f2fb9fc75c0bf1fa7606c427de4cfb4ed3d115
6
+ metadata.gz: 0f78c4e7669975915d24d7a75bcdfb1481745a01c8fbad1f0264137d55b5630d1e87173000cb86f3c2f70869dfdb5ad880a65dc5cefeea1db1d5d6895ee9ff84
7
+ data.tar.gz: da8af5b60c5f6ce8ce759838668d57127b29e9f1277f99ca90c11f45ed6169517bc6853b1f5e47d3d74958c2c112aefa8a747507f36056e69d30ad99f17e29bd
data/.codeclimate.yml ADDED
@@ -0,0 +1,2 @@
1
+ exclude_paths:
2
+ - examples/*
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.build_all("A > B", "A == B")
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 { |c| termify(c) })
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(source)
11
- decorate(rewrite(parse(source)))
12
- end
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
@@ -11,11 +11,5 @@ module Metamorpher
11
11
  def merge(src, replacements, &block)
12
12
  Transformer::Merger.new(src).merge(*replacements, &block)
13
13
  end
14
-
15
- private
16
-
17
- def replacements
18
- @replacements ||= [replacement]
19
- end
20
14
  end
21
15
  end
@@ -31,6 +31,12 @@ module Metamorpher
31
31
  derived.derivation.call(*substitutes)
32
32
  end
33
33
 
34
+ def visit_termset(termset)
35
+ Terms::TermSet.new(
36
+ terms: termset.terms.map { |term| visit(term) }
37
+ )
38
+ end
39
+
34
40
  private
35
41
 
36
42
  def substitution_for_variable(name)
@@ -20,6 +20,10 @@ module Metamorpher
20
20
  name
21
21
  end
22
22
 
23
+ def alternatives
24
+ [self]
25
+ end
26
+
23
27
  def path
24
28
  if parent
25
29
  parent.path << parent.children.index { |c| c.equal?(self) }
@@ -0,0 +1,17 @@
1
+ require "metamorpher/terms/term"
2
+
3
+ module Metamorpher
4
+ module Terms
5
+ class TermSet < Term
6
+ attributes terms: []
7
+
8
+ def inspect
9
+ "TermSet" + terms.inspect
10
+ end
11
+
12
+ def alternatives
13
+ terms
14
+ end
15
+ end
16
+ end
17
+ end
@@ -32,19 +32,19 @@ module Metamorpher
32
32
 
33
33
  def reduce_to_replacements(src, literal)
34
34
  [].tap do |replacements|
35
- rules.each do |rule| # FIXME : change to inject?
36
- rule.reduce(literal) do |original, rewritten|
37
- original_position = driver.source_location_for(original)
38
- original_code = src[original_position]
39
- transformed_code = driver.unparse(rewritten)
40
- replacements << Site.new(original_position, original_code, transformed_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 rules
47
- @rules ||= replacements.map { |r| Rewriter::Rule.new(pattern: pattern, replacement: r) }
46
+ def rule
47
+ @rule ||= Rewriter::Rule.new(pattern: pattern, replacement: replacement)
48
48
  end
49
49
  end
50
50
  end
@@ -1,3 +1,3 @@
1
1
  module Metamorpher
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -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 replacements
15
- builder.build_all("A > B", "A == B")
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 < bar\n" \
37
- " bar > baz\n" \
36
+ " foo == bar\n" \
37
+ " bar < baz\n" \
38
38
  "end",
39
39
 
40
40
  "def compare\n" \
41
- " foo == bar\n" \
42
- " bar < baz\n" \
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
@@ -0,0 +1,5 @@
1
+ require "metamorpher/builders/ast/term_set_builder"
2
+
3
+ describe Metamorpher::Builders::AST::TermSetBuilder do
4
+ it_behaves_like "a term set builder"
5
+ 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.0
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-08 00:00:00.000000000 Z
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