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 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