skeem 0.2.14 → 0.2.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +451 -195
  3. data/.travis.yml +27 -0
  4. data/CHANGELOG.md +35 -1
  5. data/Gemfile +2 -0
  6. data/README.md +125 -56
  7. data/Rakefile +2 -0
  8. data/appveyor.yml +3 -4
  9. data/bin/cubic.skm +4 -0
  10. data/bin/hello-world.skm +1 -0
  11. data/bin/skeem +72 -0
  12. data/lib/skeem/datum_dsl.rb +40 -30
  13. data/lib/skeem/element_visitor.rb +5 -2
  14. data/lib/skeem/grammar.rb +88 -26
  15. data/lib/skeem/interpreter.rb +9 -7
  16. data/lib/skeem/parser.rb +6 -4
  17. data/lib/skeem/primitive/primitive_builder.rb +148 -122
  18. data/lib/skeem/primitive/primitive_procedure.rb +23 -25
  19. data/lib/skeem/runtime.rb +17 -15
  20. data/lib/skeem/s_expr_builder.rb +49 -117
  21. data/lib/skeem/s_expr_nodes.rb +147 -132
  22. data/lib/skeem/skeem_exception.rb +1 -0
  23. data/lib/skeem/skm_binding.rb +9 -11
  24. data/lib/skeem/skm_compound_datum.rb +9 -6
  25. data/lib/skeem/skm_element.rb +15 -13
  26. data/lib/skeem/skm_empty_list.rb +6 -4
  27. data/lib/skeem/skm_exception.rb +9 -0
  28. data/lib/skeem/skm_expression.rb +3 -1
  29. data/lib/skeem/skm_frame.rb +3 -2
  30. data/lib/skeem/skm_pair.rb +26 -18
  31. data/lib/skeem/skm_procedure_exec.rb +11 -6
  32. data/lib/skeem/skm_simple_datum.rb +23 -20
  33. data/lib/skeem/skm_unary_expression.rb +34 -37
  34. data/lib/skeem/standard/base.skm +4 -0
  35. data/lib/skeem/tokenizer.rb +38 -28
  36. data/lib/skeem/version.rb +3 -1
  37. data/lib/skeem.rb +2 -0
  38. data/skeem.gemspec +9 -6
  39. data/spec/skeem/add4.skm +4 -0
  40. data/spec/skeem/datum_dsl_spec.rb +13 -12
  41. data/spec/skeem/element_visitor_spec.rb +14 -10
  42. data/spec/skeem/interpreter_spec.rb +84 -44
  43. data/spec/skeem/lambda_spec.rb +13 -11
  44. data/spec/skeem/parser_spec.rb +23 -19
  45. data/spec/skeem/primitive/primitive_builder_spec.rb +65 -48
  46. data/spec/skeem/primitive/primitive_procedure_spec.rb +14 -12
  47. data/spec/skeem/runtime_spec.rb +20 -18
  48. data/spec/skeem/s_expr_nodes_spec.rb +8 -6
  49. data/spec/skeem/skm_compound_datum_spec.rb +12 -10
  50. data/spec/skeem/skm_element_spec.rb +7 -5
  51. data/spec/skeem/skm_empty_list_spec.rb +7 -5
  52. data/spec/skeem/skm_frame_spec.rb +5 -4
  53. data/spec/skeem/skm_pair_spec.rb +9 -8
  54. data/spec/skeem/skm_procedure_exec_spec.rb +2 -0
  55. data/spec/skeem/skm_simple_datum_spec.rb +24 -22
  56. data/spec/skeem/skm_unary_expression_spec.rb +11 -9
  57. data/spec/skeem/tokenizer_spec.rb +54 -43
  58. data/spec/skeem_spec.rb +2 -0
  59. data/spec/spec_helper.rb +15 -10
  60. metadata +18 -10
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'skm_expression'
2
4
 
3
5
  module Skeem
@@ -29,7 +31,7 @@ module Skeem
29
31
  super(nil, aDatum)
30
32
  end
31
33
 
32
- def evaluate(aRuntime)
34
+ def evaluate(_runtime)
33
35
  datum
34
36
  end
35
37
 
@@ -47,7 +49,6 @@ module Skeem
47
49
  def inspect_specific
48
50
  datum.inspect
49
51
  end
50
-
51
52
  end # class
52
53
 
53
54
  class SkmQuasiquotation < SkmQuotation
@@ -60,7 +61,6 @@ module Skeem
60
61
  def quasiquote(aRuntime)
61
62
  child.quasiquote(aRuntime)
62
63
  end
63
-
64
64
  end # class
65
65
 
66
66
  class SkmUnquotation < SkmUnaryExpression
@@ -76,8 +76,7 @@ module Skeem
76
76
  end
77
77
 
78
78
  def quasiquote(aRuntime)
79
- result = evaluate(aRuntime)
80
- result
79
+ evaluate(aRuntime)
81
80
  end
82
81
 
83
82
  protected
@@ -87,7 +86,7 @@ module Skeem
87
86
  end
88
87
  end # class
89
88
 
90
- class SkmVariableReference < SkmUnaryExpression
89
+ class SkmVariableReference < SkmUnaryExpression
91
90
  alias variable child
92
91
 
93
92
  def eqv?(other)
@@ -100,13 +99,13 @@ module Skeem
100
99
  aRuntime.evaluate(var_key)
101
100
  end
102
101
 
103
- def quasiquote(aRuntime)
102
+ def quasiquote(_runtime)
104
103
  self
105
104
  end
106
105
 
107
106
  # Confusing!
108
107
  # Value, here, means the value of the identifier (the variable's name).
109
- def value()
108
+ def value
110
109
  variable.value
111
110
  end
112
111
  end # class
@@ -115,7 +114,10 @@ module Skeem
115
114
  class SkmBindingBlock < SkmUnaryExpression
116
115
  alias body child
117
116
 
117
+ # @return [Symbol] One of: :let, :let_star
118
118
  attr_reader :kind
119
+
120
+ # @return [Array<SkmBinding>]
119
121
  attr_reader :bindings
120
122
 
121
123
  def initialize(theKind, theBindings, aBody)
@@ -126,14 +128,15 @@ module Skeem
126
128
 
127
129
  def evaluate(aRuntime)
128
130
  aRuntime.push(SkmFrame.new(aRuntime.environment))
129
- if kind == :let
131
+ case kind
132
+ when :let
130
133
  locals = bindings.map do |bnd|
131
134
  SkmBinding.new(bnd.variable, bnd.value.evaluate(aRuntime))
132
135
  end
133
136
  locals.each do |bnd|
134
137
  aRuntime.add_binding(bnd.variable.evaluate(aRuntime), bnd.value)
135
138
  end
136
- elsif kind == :let_star
139
+ when :let_star
137
140
  bindings.each do |bnd|
138
141
  val = bnd.value.evaluate(aRuntime)
139
142
  aRuntime.add_binding(bnd.variable.evaluate(aRuntime), val)
@@ -155,7 +158,6 @@ module Skeem
155
158
  # $stderr.puts "Result SkmBindingBlock#evaluate: " + result.inspect
156
159
  result
157
160
  end
158
-
159
161
  end # class
160
162
 
161
163
  # Sequencing construct
@@ -187,18 +189,16 @@ module Skeem
187
189
  def eval_pair(aRuntime)
188
190
  result = nil
189
191
  sequence.to_a.each do |cmd|
190
- begin
191
- if cmd.kind_of?(SkmLambda)
192
- result = cmd.dup_cond(aRuntime)
193
- else
194
- result = cmd.evaluate(aRuntime)
195
- end
196
- rescue NoMethodError => exc
197
- $stderr.puts self.inspect
198
- $stderr.puts sequence.inspect
199
- $stderr.puts cmd.inspect
200
- raise exc
192
+ if cmd.kind_of?(SkmLambda)
193
+ result = cmd.dup_cond(aRuntime)
194
+ else
195
+ result = cmd.evaluate(aRuntime)
201
196
  end
197
+ rescue NoMethodError => e
198
+ $stderr.puts inspect
199
+ $stderr.puts sequence.inspect
200
+ $stderr.puts cmd.inspect
201
+ raise e
202
202
  end
203
203
 
204
204
  result
@@ -216,27 +216,24 @@ module Skeem
216
216
 
217
217
  if sequence[:sequence].kind_of?(SkmPair)
218
218
  sequence[:sequence].to_a.each do |cmd|
219
- begin
220
- if cmd.kind_of?(SkmLambda)
221
- result = cmd.dup_cond(aRuntime)
222
- else
223
- result = cmd.evaluate(aRuntime)
224
- end
225
- rescue NoMethodError => exc
226
- $stderr.puts self.inspect
227
- $stderr.puts sequence[:sequence].inspect
228
- $stderr.puts cmd.inspect
229
- raise exc
219
+ if cmd.kind_of?(SkmLambda)
220
+ result = cmd.dup_cond(aRuntime)
221
+ else
222
+ result = cmd.evaluate(aRuntime)
230
223
  end
224
+ rescue NoMethodError => e
225
+ $stderr.puts inspect
226
+ $stderr.puts sequence[:sequence].inspect
227
+ $stderr.puts cmd.inspect
228
+ raise e
231
229
  end
232
- elsif
230
+ else
233
231
  result = sequence.evaluate(aRuntime)
234
232
  end
235
233
 
236
234
  aRuntime.pop
237
-
235
+
238
236
  result
239
237
  end
240
238
  end # class
241
-
242
- end # module
239
+ end # module
@@ -140,6 +140,10 @@
140
140
 
141
141
  (define symbol=? string=?)
142
142
 
143
+ (define newline
144
+ (lambda ()
145
+ (display #\newline)))
146
+
143
147
  ;; Test the equivalence (with eqv? predicate) between an expected value and
144
148
  ;; an expression
145
149
  ;; (test-eqv expected test-expr)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # File: tokenizer.rb
2
4
  # Tokenizer for Skeem language (a small subset of Scheme)
3
5
  require 'strscan'
@@ -14,8 +16,13 @@ module Skeem
14
16
  # Delimiters: parentheses '(', ')'
15
17
  # Separators: comma
16
18
  class Tokenizer
19
+ # @return [StringScanner]
17
20
  attr_reader(:scanner)
21
+
22
+ # @return [Integer] Current line number
18
23
  attr_reader(:lineno)
24
+
25
+ # @return [Integer] Offset of start of current line
19
26
  attr_reader(:line_start)
20
27
 
21
28
  @@lexeme2name = {
@@ -25,9 +32,11 @@ module Skeem
25
32
  '(' => 'LPAREN',
26
33
  ')' => 'RPAREN',
27
34
  '.' => 'PERIOD',
35
+ '...' => 'ELLIPSIS',
28
36
  ',' => 'COMMA',
29
37
  ',@' => 'COMMA_AT_SIGN',
30
- '#(' => 'VECTOR_BEGIN'
38
+ '#(' => 'VECTOR_BEGIN',
39
+ '_' => 'UNDERSCORE'
31
40
  }.freeze
32
41
 
33
42
  # Here are all the implemented Scheme keywords (in uppercase)
@@ -35,18 +44,21 @@ module Skeem
35
44
  BEGIN
36
45
  COND
37
46
  DEFINE
47
+ DEFINE-SYNTAX
38
48
  DO
39
49
  ELSE
40
50
  IF
51
+ INCLUDE
41
52
  LAMBDA
42
53
  LET
43
54
  LET*
44
55
  QUASIQUOTE
45
56
  QUOTE
46
57
  SET!
58
+ SYNTAX-RULES
47
59
  UNQUOTE
48
60
  UNQUOTE-SPLICING
49
- ].map { |x| [x, x] } .to_h
61
+ ].map { |x| [x, x.sub(/\*$/, '_STAR')] }.to_h
50
62
 
51
63
  class ScanError < StandardError; end
52
64
 
@@ -57,7 +69,6 @@ module Skeem
57
69
  reinitialize(source)
58
70
  end
59
71
 
60
-
61
72
  # @param source [String] Skeem text to tokenize.
62
73
  def reinitialize(source)
63
74
  @scanner.string = source
@@ -88,9 +99,9 @@ module Skeem
88
99
  if "()'`".include? curr_ch
89
100
  # Delimiters, separators => single character token
90
101
  token = build_token(@@lexeme2name[curr_ch], scanner.getch)
91
- elsif (lexeme = scanner.scan(/(?:\.)(?=\s)/)) # Single char occurring alone
92
- token = build_token('PERIOD', lexeme)
93
- elsif (lexeme = scanner.scan(/(?:,@?)|(?:=>)/))
102
+ elsif (lexeme = scanner.scan(/(?:\.|_)(?=\s|\()/)) # Single char occurring alone
103
+ token = build_token(@@lexeme2name[curr_ch], scanner.getch)
104
+ elsif (lexeme = scanner.scan(/(?:,@?)|(?:=>)|(?:\.\.\.)/))
94
105
  token = build_token(@@lexeme2name[lexeme], lexeme)
95
106
  elsif (token = recognize_char_token)
96
107
  # Do nothing
@@ -99,17 +110,17 @@ module Skeem
99
110
  elsif (lexeme = scanner.scan(/(?:#[dD])?[+-]?[0-9]+(?:.0+)?(?=\s|[|()";]|$)/))
100
111
  token = build_token('INTEGER', lexeme) # Decimal radix
101
112
  elsif (lexeme = scanner.scan(/#[xX][+-]?[0-9a-fA-F]+(?=\s|[|()";]|$)/))
102
- token = build_token('INTEGER', lexeme) # Hexadecimal radix
113
+ token = build_token('INTEGER', lexeme) # Hexadecimal radix
103
114
  elsif (lexeme = scanner.scan(/[+-]?[0-9]+(?:\.[0-9]*)?(?:(?:e|E)[+-]?[0-9]+)?/))
104
115
  # Order dependency: must be tested after INTEGER case
105
116
  token = build_token('REAL', lexeme)
106
117
  elsif (lexeme = scanner.scan(/#(?:(?:true)|(?:false)|(?:u8)|[\\\(tfeiodx]|(?:\d+[=#]))/))
107
- token = cardinal_token(lexeme)
118
+ token = cardinal_token(lexeme)
108
119
  elsif (lexeme = scanner.scan(/"(?:\\"|[^"])*"/)) # Double quotes literal?
109
120
  token = build_token('STRING_LIT', lexeme)
110
121
  elsif (lexeme = scanner.scan(/[a-zA-Z!$%&*\/:<=>?@^_~][a-zA-Z0-9!$%&*+-.\/:<=>?@^_~+-]*/))
111
122
  keyw = @@keywords[lexeme.upcase]
112
- tok_type = keyw ? keyw : 'IDENTIFIER'
123
+ tok_type = keyw || 'IDENTIFIER'
113
124
  token = build_token(tok_type, lexeme)
114
125
  elsif (lexeme = scanner.scan(/\|(?:[^|])*\|/)) # Vertical bar delimited
115
126
  token = build_token('IDENTIFIER', lexeme)
@@ -151,17 +162,16 @@ other literal data (section 2.4).
151
162
  return token
152
163
  end
153
164
 
154
- def recognize_char_token()
165
+ def recognize_char_token
155
166
  token = nil
156
- if lexeme = scanner.scan(/#\\/)
157
- if lexeme = scanner.scan(/(?:alarm|backspace|delete|escape|newline|null|return|space|tab)/)
167
+ if (lexeme = scanner.scan(/#\\/))
168
+ if (lexeme = scanner.scan(/(?:alarm|backspace|delete|escape|newline|null|return|space|tab)/))
158
169
  token = build_token('CHAR', lexeme, :name)
159
- elsif lexeme = scanner.scan(/[^x]/)
160
- token = build_token('CHAR', lexeme, :escaped)
161
- elsif lexeme = scanner.scan(/x[0-9a-fA-F]+/)
170
+ elsif (lexeme = scanner.scan(/x[0-9a-fA-F]+/))
162
171
  token = build_token('CHAR', lexeme, :hex_value)
163
- elsif lexeme = scanner.scan(/x/)
164
- token = build_token('CHAR', lexeme, :escaped)
172
+ else
173
+ lexeme = scanner.getch
174
+ token = build_token('CHAR', lexeme, :escaped)
165
175
  end
166
176
  end
167
177
 
@@ -174,9 +184,9 @@ other literal data (section 2.4).
174
184
  col = scanner.pos - aLexeme.size - @line_start + 1
175
185
  pos = Rley::Lexical::Position.new(@lineno, col)
176
186
  token = Rley::Lexical::Token.new(value, symb, pos)
177
- rescue StandardError => exc
187
+ rescue StandardError => e
178
188
  puts "Failing with '#{aSymbolName}' and '#{aLexeme}'"
179
- raise exc
189
+ raise e
180
190
  end
181
191
 
182
192
  return token
@@ -207,11 +217,11 @@ other literal data (section 2.4).
207
217
  return [value, symb]
208
218
  end
209
219
 
210
- def to_boolean(aLexeme, aFormat)
211
- result = (aLexeme =~ /^#t/) ? true : false
220
+ def to_boolean(aLexeme, _format)
221
+ (aLexeme =~ /^#t/) ? true : false
212
222
  end
213
223
 
214
- def to_integer(aLexeme, aFormat)
224
+ def to_integer(aLexeme, _format)
215
225
  literal = aLexeme.downcase
216
226
  prefix_pattern = /^#[dx]/
217
227
  matching = literal.match(prefix_pattern)
@@ -226,7 +236,7 @@ other literal data (section 2.4).
226
236
  else
227
237
  format = :default
228
238
  end
229
-
239
+
230
240
  case format
231
241
  when :default, :base10
232
242
  value = literal.to_i
@@ -303,11 +313,11 @@ other literal data (section 2.4).
303
313
 
304
314
  name2char[name]
305
315
  end
306
-
316
+
307
317
  def escaped_char(aLexeme)
308
318
  aLexeme.chr
309
319
  end
310
-
320
+
311
321
  def hex_value_char(aLexeme)
312
322
  hex_literal = aLexeme.sub(/^x/, '')
313
323
  hex_value = hex_literal.to_i(16)
@@ -338,15 +348,14 @@ other literal data (section 2.4).
338
348
  skip_block_comment
339
349
  next
340
350
  end
341
- break unless ws_found or cmt_found
351
+ break unless ws_found || cmt_found
342
352
  end
343
353
 
344
354
  curr_pos = scanner.pos
345
355
  return if curr_pos == pre_pos
346
356
  end
347
357
 
348
- def skip_block_comment()
349
- # require 'debug'
358
+ def skip_block_comment
350
359
  scanner.skip(/#\|/)
351
360
  nesting_level = 1
352
361
  loop do
@@ -354,6 +363,7 @@ other literal data (section 2.4).
354
363
  unless comment_part
355
364
  raise ScanError, "Unterminated '#| ... |#' comment on line #{lineno}"
356
365
  end
366
+
357
367
  case scanner.matched
358
368
  when /(?:(?:\r\n)|\r|\n)/
359
369
  next_line
data/lib/skeem/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Skeem
2
- VERSION = '0.2.14'.freeze
4
+ VERSION = '0.2.18'
3
5
  end
data/lib/skeem.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # File: skeem.rb
2
4
  # This file acts as a jumping-off point for loading dependencies expected
3
5
  # for a Skeem client.
data/skeem.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path('../lib', __FILE__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require 'skeem/version'
@@ -8,6 +10,7 @@ module PkgExtending
8
10
  file_list = Dir[
9
11
  '.rubocop.yml',
10
12
  '.rspec',
13
+ '.travis.yml',
11
14
  '.yardopts',
12
15
  'appveyor.yml',
13
16
  'Gemfile',
@@ -16,11 +19,13 @@ module PkgExtending
16
19
  'LICENSE.txt',
17
20
  'README.md',
18
21
  'skeem.gemspec',
19
- 'bin/*.rb',
22
+ 'bin/*.*',
20
23
  'lib/*.*',
21
24
  'lib/**/*.rb',
22
25
  'lib/**/*.skm',
23
26
  'spec/**/*.rb',
27
+ 'spec/**/*.skm',
28
+ 'spec/**/*.yml'
24
29
  ]
25
30
  aPackage.files = file_list
26
31
  aPackage.test_files = Dir['spec/**/*_spec.rb']
@@ -49,20 +54,18 @@ DESCR
49
54
  SUMMARY
50
55
  spec.homepage = 'https://github.com/famished-tiger/Skeem'
51
56
  spec.license = 'MIT'
57
+ spec.required_ruby_version = '>= 2.5.0'
52
58
 
53
59
  spec.bindir = 'bin'
60
+ spec.executables << 'skeem'
54
61
  spec.require_paths = ['lib']
55
62
  PkgExtending.pkg_files(spec)
56
63
  PkgExtending.pkg_documentation(spec)
57
64
  # Runtime dependencies
58
- spec.add_dependency 'rley', '~> 0.7'
65
+ spec.add_dependency 'rley', '~> 0.8.03'
59
66
 
60
67
  # Development dependencies
61
- if RUBY_VERSION <= '2.2'
62
- spec.add_development_dependency 'bundler', '~> 1.16'
63
- else
64
68
  spec.add_development_dependency 'bundler', '~> 2.0'
65
- end
66
69
  spec.add_development_dependency 'rake', '~> 12.0'
67
70
  spec.add_development_dependency 'rspec', '~> 3.0'
68
71
  end
@@ -0,0 +1,4 @@
1
+ (define add4
2
+ (let ((x 4))
3
+ (lambda (y) (+ x y))))
4
+ (add4 6) ; => 10
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
 
3
5
 
@@ -35,15 +37,15 @@ module Skeem
35
37
  ['+456', 456]
36
38
  ]
37
39
  end
38
-
40
+
39
41
  let(:rational_tests) do
40
42
  [
41
- [-Rational(2,3), -Rational(2, 3)],
43
+ [-Rational(2, 3), -Rational(2, 3)],
42
44
  [Rational(22, 7), Rational(22, 7)],
43
45
  ['-2/3', -Rational(2, 3)],
44
46
  ['+22/7', Rational(22, 7)]
45
47
  ]
46
- end
48
+ end
47
49
 
48
50
  let(:real_tests) do
49
51
  [
@@ -60,13 +62,13 @@ module Skeem
60
62
 
61
63
  let(:string_tests) do
62
64
  [
63
- ['hello', 'hello']
65
+ %w[hello hello]
64
66
  ]
65
67
  end
66
68
 
67
69
  let(:identifier_tests) do
68
70
  [
69
- ['define', 'define'],
71
+ %w[define define],
70
72
  [SkmString.create('positive?'), 'positive?']
71
73
  ]
72
74
  end
@@ -76,7 +78,7 @@ module Skeem
76
78
  ['#t', SkmBoolean.create(true)],
77
79
  [-1, SkmInteger.create(-1)],
78
80
  [1.41, 1.41],
79
- ['foo', 'foo']
81
+ %w[foo foo]
80
82
  ]
81
83
  end
82
84
 
@@ -92,12 +94,12 @@ module Skeem
92
94
  expect(subject.integer(literal)).to eq(predicted)
93
95
  end
94
96
  end
95
-
97
+
96
98
  it 'should convert rational literals' do
97
99
  rational_tests.each do |(literal, predicted)|
98
100
  expect(subject.rational(literal)).to eq(predicted)
99
101
  end
100
- end
102
+ end
101
103
 
102
104
  it 'should convert real number literals' do
103
105
  real_tests.each do |(literal, predicted)|
@@ -178,12 +180,12 @@ module Skeem
178
180
  expect(subject.to_datum(literal)).to eq(predicted)
179
181
  end
180
182
  end
181
-
183
+
182
184
  it 'should recognize & convert rational literals' do
183
185
  rational_tests.each do |(literal, predicted)|
184
186
  expect(subject.to_datum(literal)).to eq(predicted)
185
187
  end
186
- end
188
+ end
187
189
 
188
190
  it 'should recognize & convert real number literals' do
189
191
  real_tests.each do |(literal, predicted)|
@@ -219,6 +221,5 @@ module Skeem
219
221
  # $stderr.puts duplicate.inspect
220
222
  expect(duplicate.to_a).to eq([f, g])
221
223
  end
222
-
223
224
  end # describe
224
- end # module
225
+ end # module