skeem 0.2.09 → 0.2.10

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
  SHA256:
3
- metadata.gz: a85fc0a12d54eedba56c28e18d728d00ff35044c029e1a256d4381a98c6310a8
4
- data.tar.gz: 934cc810162a891fdddffdbfb16466ed7d7c71da8d7c5cb77670c010b5bf83fe
3
+ metadata.gz: 482a0c8f936db1cc3099e133dda1d42d685d5e3c7fe244bf98f0062ad1470bc1
4
+ data.tar.gz: e950b89cf3a4bc7971a5263e96f7a8dbb670d926b03601d2c4116ccc03b613ec
5
5
  SHA512:
6
- metadata.gz: 6dcdf9eb8d85abdcd4d2f416b9e9fa6ed1d9b399b92c9921ff59fb8dc6d6f047e5064fd7ccffde3ae000771079a0a7d9f64643e0585ba0c39b0566b345a5b994
7
- data.tar.gz: 60347248374f6723c91edc3699b1306056b02bfc7a91a52e9dcfa15690f72294bf89cfd1ef8551218a6e67751bdde59f972c6ec1ea0e42733436d7caa5b26bac
6
+ metadata.gz: a8a6cde2cd049ccdf7ac4256b2313a462990f355790e6a23ab78febaa9f2377a39914f169c213e5ec1b2342e6bc611eeddae6f8f806ac3ecff713a356765403c
7
+ data.tar.gz: 6b9ea76b548deb643631515a755c431a30c915ad1db1dee609ceec1c03e17380d99f0fe33d1ab80a4eda4eec9de3a4acc55e03d6f8d4ebac800f650ad9695554
@@ -1,3 +1,24 @@
1
+ ## [0.2.10] - 2019-06-15
2
+ - Skeem now supports rational numbers (fractions)
3
+ - Added procedures: `max`, `min`, `floor/`, `floor-quotient`, `floor-remainder`, `truncate/`, `truncate-quotient`,
4
+ `truncate-remainder`, `quotient`, `remainder`, `modulo`
5
+
6
+ ### Added
7
+ - `DatumDSL#rational` conversion method
8
+
9
+ ### Changed
10
+ - `DatumDSL#to_datum(aLiteral): added conversion of literal rational into rational value.
11
+ - File `grammar.rb` Added new terminal RATIONAL and rule deriving number from rational
12
+ - File `primitive_builder.rb` Implemented primitive procedures floor/ and truncate/
13
+ - Class `SkmInteger` now inherits from `SkmRational` class
14
+ - File `base.skm` added implementation of `floor-quotient`, `floor-remainder`, `truncate-quotient`,
15
+ `truncate-remainder`, `quotient`, `remainder`, `modulo`
16
+ - Class Tokenizer updated to recognize rational numbers.
17
+ - Test suite file `base_tests.scm` expanded.
18
+
19
+ ### Fixed
20
+ - File `primitive_builder.rb` Fixed and extended implementation of `/`procedure
21
+
1
22
  ## [0.2.09] - 2019-06-10
2
23
  - New procedures: `complex?`, `exact-integer?`
3
24
  - Support for `#| ... |#` block comments (including nesting)
data/README.md CHANGED
@@ -174,6 +174,7 @@ Here are a few pointers for the Scheme programming language:
174
174
  - Booleans: `#t`, `#true`, `#f`, `#false`
175
175
  - Of the number hierarchy:
176
176
  `real` (e.g. 2.718, 6.671e-11),
177
+ `rational` (e.g. 22/7, 1/137, -13/41)
177
178
  `integer` (42, -3)
178
179
  - Lists (quoted) : '(1 two "three")
179
180
  - Strings: `"Hello, world."`
@@ -250,9 +251,10 @@ This section lists the implemented standard procedures
250
251
  * `boolean?`, `and`, `or`, `not`
251
252
 
252
253
  #### Numerical operations
253
- * Number-level: `number?`, `complex?`, `real?`, `integer?`, `zero?`, `exact?`, `inexact?`, `exact-integer?` , `+`, `-`, `*`, `/`,
254
+ * Number-level: `number?`, `complex?`, `real?`, `rational?`, `integer?`, `zero?`, `exact?`, `inexact?`, `exact-integer?` , `+`, `-`, `*`, `/`,
254
255
  `=`, `square`, `number->string`
255
- * Real-level: `positive?`, `negative?`, `<`, `>`, `<=`, `>=`, `abs`, `floor-remainder`
256
+ * Real-level: `positive?`, `negative?`, `<`, `>`, `<=`, `>=`, `abs`, `max`, `min`, `floor/`, `floor-quotient`, `floor-remainder`, `truncate/`, `truncate-quotient`,
257
+ `truncate-remainder`, `quotient`, `remainder`, `modulo`
256
258
  * Integer-level: `even?`, `odd?`
257
259
 
258
260
  #### List procedures
@@ -33,6 +33,19 @@ module Skeem
33
33
  raise StandardError, aLiteral.inspect
34
34
  end
35
35
  end
36
+
37
+ def rational(aLiteral)
38
+ return aLiteral if aLiteral.kind_of?(SkmRational)
39
+
40
+ result = case aLiteral
41
+ when Rational
42
+ SkmRational.create(aLiteral)
43
+ when /^[+-]?\d+\/\d+$/
44
+ SkmRational.create(Rational(aLiteral))
45
+ else
46
+ raise StandardError, aLiteral.inspect
47
+ end
48
+ end
36
49
 
37
50
  def real(aLiteral)
38
51
  return aLiteral if aLiteral.kind_of?(SkmReal)
@@ -112,6 +125,8 @@ module Skeem
112
125
  aLiteral.map { |elem| to_datum(elem) }
113
126
  when Integer
114
127
  SkmInteger.create(aLiteral)
128
+ when Rational
129
+ SkmRational.create(aLiteral)
115
130
  when Float
116
131
  SkmReal.create(aLiteral)
117
132
  when TrueClass, FalseClass
@@ -133,6 +148,8 @@ module Skeem
133
148
  boolean(aLiteral)
134
149
  elsif aLiteral =~ /^#f(?:alse)?|false$/
135
150
  boolean(aLiteral)
151
+ elsif aLiteral =~ /^[+-]?\d+\/\d+$/
152
+ rational(aLiteral)
136
153
  elsif aLiteral =~ /^[+-]?\d+$/
137
154
  integer(aLiteral)
138
155
  elsif aLiteral =~ /^[+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?$/
@@ -15,8 +15,8 @@ module Skeem
15
15
  add_terminals('VECTOR_BEGIN')
16
16
 
17
17
  # Literal values...
18
- add_terminals('BOOLEAN', 'INTEGER', 'REAL')
19
- add_terminals('STRING_LIT', 'IDENTIFIER')
18
+ add_terminals('BOOLEAN', 'INTEGER', 'RATIONAL')
19
+ add_terminals('REAL', 'STRING_LIT', 'IDENTIFIER')
20
20
 
21
21
  # Keywords...
22
22
  add_terminals('BEGIN', 'COND', 'DEFINE', 'ELSE')
@@ -93,6 +93,7 @@ module Skeem
93
93
  rule 'alternate' => 'expression'
94
94
  rule 'alternate' => []
95
95
  rule 'number' => 'INTEGER'
96
+ rule 'number' => 'RATIONAL'
96
97
  rule 'number' => 'REAL'
97
98
  rule('assignment' => 'LPAREN SET! IDENTIFIER expression RPAREN').as 'assignment'
98
99
  rule('derived_expression' => 'LPAREN COND cond_clause_plus RPAREN').as 'cond_form'
@@ -51,7 +51,8 @@ module Skeem
51
51
  create_minus(aRuntime)
52
52
  create_multiply(aRuntime)
53
53
  create_divide(aRuntime)
54
- create_modulo(aRuntime)
54
+ create_floor_slash(aRuntime)
55
+ create_truncate_slash(aRuntime)
55
56
  end
56
57
 
57
58
  def add_comparison(aRuntime)
@@ -63,12 +64,15 @@ module Skeem
63
64
  create_gt(aRuntime)
64
65
  create_lte(aRuntime)
65
66
  create_gte(aRuntime)
67
+ create_max(aRuntime)
68
+ create_min(aRuntime)
66
69
  end
67
70
 
68
71
  def add_number_procedures(aRuntime)
69
72
  create_object_predicate(aRuntime, 'number?')
70
73
  create_object_predicate(aRuntime, 'complex?')
71
74
  create_object_predicate(aRuntime, 'real?')
75
+ create_object_predicate(aRuntime, 'rational?')
72
76
  create_object_predicate(aRuntime, 'integer?')
73
77
  create_object_predicate(aRuntime, 'exact?')
74
78
  create_number2string(aRuntime)
@@ -178,19 +182,47 @@ module Skeem
178
182
  define_primitive_proc(aRuntime, '*', zero_or_more, primitive)
179
183
  end
180
184
 
185
+ def reciprocal(aLiteral)
186
+
187
+ case aLiteral
188
+ when Integer
189
+ result = Rational(1, aLiteral)
190
+ when Rational
191
+ result = Rational(aLiteral.denominator, aLiteral.numerator)
192
+ else
193
+ result = 1 / aLiteral.to_f
194
+ end
195
+
196
+ result
197
+ end
181
198
 
182
199
  def create_divide(aRuntime)
183
200
  primitive = ->(_runtime, first_operand, arglist) do
184
201
  raw_result = first_operand.value
185
202
  if arglist.empty?
186
- raw_result = 1 / raw_result.to_f
203
+ raw_result = reciprocal(raw_result)
187
204
  else
205
+ # Ugly: Ruby version dependency: Rubies older than 2.4 have class Fixnum instead of Integer
206
+ int_class = (RUBY_VERSION[0..2] < "2.4") ? Fixnum : Integer
207
+
188
208
  arglist.each do |elem|
189
- if raw_result > elem.value && raw_result.modulo(elem.value).zero?
190
- raw_result /= elem.value
191
- else
192
- raw_result = raw_result.to_f
193
- raw_result /= elem.value
209
+ elem_value = elem.value
210
+ case [raw_result.class, elem_value.class]
211
+ when [int_class, int_class]
212
+ if raw_result.modulo(elem_value).zero?
213
+ raw_result = raw_result / elem_value
214
+ else
215
+ raw_result = Rational(raw_result, elem_value)
216
+ end
217
+
218
+ when [int_class, Rational]
219
+ raw_result = raw_result * reciprocal(elem_value)
220
+
221
+ when [Rational, Rational]
222
+ raw_result = raw_result * reciprocal(elem_value)
223
+ else
224
+ raw_result = raw_result.to_f
225
+ raw_result /= elem_value
194
226
  end
195
227
  end
196
228
  end
@@ -200,13 +232,24 @@ module Skeem
200
232
  define_primitive_proc(aRuntime, '/', one_or_more, primitive)
201
233
  end
202
234
 
203
- def create_modulo(aRuntime)
235
+ def create_floor_slash(aRuntime)
204
236
  primitive = ->(_runtime, operand_1, operand_2) do
205
- raw_result = operand_1.value.modulo(operand_2.value)
206
- to_datum(raw_result)
237
+ (quotient, modulus) = operand_1.value.divmod(operand_2.value)
238
+ SkmPair.new(to_datum(quotient), to_datum(modulus)) # improper list!
239
+ end
240
+
241
+ define_primitive_proc(aRuntime, 'floor/', binary, primitive)
242
+ end
243
+
244
+ def create_truncate_slash(aRuntime)
245
+ primitive = ->(_runtime, operand_1, operand_2) do
246
+ modulo_ = operand_1.value / operand_2.value
247
+ modulo_ += 1 if modulo_ < 0
248
+ remainder_ = operand_1.value.remainder(operand_2.value)
249
+ SkmPair.new(to_datum(modulo_), to_datum(remainder_)) # improper list!
207
250
  end
208
251
 
209
- define_primitive_proc(aRuntime, 'floor-remainder', binary, primitive)
252
+ define_primitive_proc(aRuntime, 'truncate/', binary, primitive)
210
253
  end
211
254
 
212
255
  def core_eqv?(eval_arg1, eval_arg2)
@@ -309,7 +352,7 @@ module Skeem
309
352
 
310
353
  define_primitive_proc(aRuntime, '>=', one_or_more, primitive)
311
354
  end
312
-
355
+
313
356
  def primitive_comparison(operator, _runtime, first_operand, arglist)
314
357
  operands = [first_operand].concat(arglist)
315
358
  result = true
@@ -317,7 +360,43 @@ module Skeem
317
360
  result &&= elem1.value.send(operator, elem2.value)
318
361
  end
319
362
 
320
- boolean(result)
363
+ boolean(result)
364
+ end
365
+
366
+ def create_max(aRuntime)
367
+ primitive = ->(_runtime, first_operand, arglist) do
368
+ if arglist.empty?
369
+ result = first_operand
370
+ else
371
+ arr = arglist.to_a
372
+ arr.unshift(first_operand)
373
+ result = arr.max do |a, b|
374
+ a.value <=> b.value if a.real? && b.real?
375
+ end
376
+ end
377
+
378
+ result
379
+ end
380
+
381
+ define_primitive_proc(aRuntime, 'max', one_or_more, primitive)
382
+ end
383
+
384
+ def create_min(aRuntime)
385
+ primitive = ->(_runtime, first_operand, arglist) do
386
+ if arglist.empty?
387
+ result = first_operand
388
+ else
389
+ arr = arglist.to_a
390
+ arr.unshift(first_operand)
391
+ result = arr.min do |a, b|
392
+ a.value <=> b.value if a.real? && b.real?
393
+ end
394
+ end
395
+
396
+ result
397
+ end
398
+
399
+ define_primitive_proc(aRuntime, 'min', one_or_more, primitive)
321
400
  end
322
401
 
323
402
  def create_number2string(aRuntime)
@@ -16,6 +16,7 @@ module Skeem
16
16
  'BOOLEAN' => SkmBoolean,
17
17
  'IDENTIFIER' => SkmIdentifier,
18
18
  'INTEGER' => SkmInteger,
19
+ 'RATIONAL' => SkmRational,
19
20
  'REAL' => SkmReal,
20
21
  'STRING_LIT' => SkmString
21
22
  }.freeze
@@ -12,10 +12,18 @@ module Skeem
12
12
  def number?
13
13
  false
14
14
  end
15
+
16
+ def complex?
17
+ false
18
+ end
15
19
 
16
20
  def real?
17
21
  false
18
22
  end
23
+
24
+ def rational?
25
+ false
26
+ end
19
27
 
20
28
  def integer?
21
29
  false
@@ -132,15 +132,21 @@ module Skeem
132
132
  false
133
133
  end
134
134
  end # class
135
-
136
- class SkmInteger < SkmReal
137
- def integer?
135
+
136
+ class SkmRational < SkmReal
137
+ def rational?
138
138
  true
139
139
  end
140
140
 
141
141
  def exact?
142
142
  true
143
143
  end
144
+ end # class
145
+
146
+ class SkmInteger < SkmRational
147
+ def integer?
148
+ true
149
+ end
144
150
  end # class
145
151
 
146
152
  class SkmString < SkmSimpleDatum
@@ -36,8 +36,34 @@
36
36
  (define inexact?
37
37
  (lambda (z)
38
38
  (not (exact? z))))
39
-
40
- ; For backwards compatibility
39
+
40
+ (define floor-quotient
41
+ (lambda (n1 n2)
42
+ (car (floor/ n1 n2))))
43
+
44
+ (define floor-remainder
45
+ (lambda (n1 n2)
46
+ (cdr (floor/ n1 n2))))
47
+
48
+ (define truncate-quotient
49
+ (lambda (n1 n2)
50
+ (car (truncate/ n1 n2))))
51
+
52
+ (define truncate-remainder
53
+ (lambda (n1 n2)
54
+ (cdr (truncate/ n1 n2))))
55
+
56
+ ; For R5RS compatibility
57
+ (define quotient
58
+ (lambda (x y)
59
+ (truncate-quotient x y)))
60
+
61
+ ; For R5RS compatibility
62
+ (define remainder
63
+ (lambda (x y)
64
+ (truncate-remainder x y)))
65
+
66
+ ; For R5RS compatibility
41
67
  (define modulo
42
68
  (lambda (x y)
43
69
  (floor-remainder x y)))
@@ -93,6 +93,8 @@ module Skeem
93
93
  token = build_token(@@lexeme2name[lexeme], lexeme)
94
94
  elsif (lexeme = scanner.scan(/#(?:(?:true)|(?:false)|(?:u8)|[\\\(tfeiodx]|(?:\d+[=#]))/))
95
95
  token = cardinal_token(lexeme)
96
+ elsif (lexeme = scanner.scan(/[+-]?[0-9]+\/[0-9]+(?=\s|[|()";]|$)/))
97
+ token = build_token('RATIONAL', lexeme) # Decimal radix
96
98
  elsif (lexeme = scanner.scan(/[+-]?[0-9]+(?:.0+)?(?=\s|[|()";]|$)/))
97
99
  token = build_token('INTEGER', lexeme) # Decimal radix
98
100
  elsif (lexeme = scanner.scan(/[+-]?[0-9]+(?:\.[0-9]*)?(?:(?:e|E)[+-]?[0-9]+)?/))
@@ -152,10 +154,10 @@ other literal data (section 2.4).
152
154
 
153
155
  def build_token(aSymbolName, aLexeme, aFormat = :default)
154
156
  begin
155
- value = convert_to(aLexeme, aSymbolName, aFormat)
157
+ (value, symb) = convert_to(aLexeme, aSymbolName, aFormat)
156
158
  col = scanner.pos - aLexeme.size - @line_start + 1
157
159
  pos = Rley::Lexical::Position.new(@lineno, col)
158
- token = Rley::Lexical::Token.new(value, aSymbolName, pos)
160
+ token = Rley::Lexical::Token.new(value, symb, pos)
159
161
  rescue StandardError => exc
160
162
  puts "Failing with '#{aSymbolName}' and '#{aLexeme}'"
161
163
  raise exc
@@ -165,11 +167,15 @@ other literal data (section 2.4).
165
167
  end
166
168
 
167
169
  def convert_to(aLexeme, aSymbolName, aFormat)
170
+ symb = aSymbolName
168
171
  case aSymbolName
169
172
  when 'BOOLEAN'
170
173
  value = to_boolean(aLexeme, aFormat)
171
174
  when 'INTEGER'
172
175
  value = to_integer(aLexeme, aFormat)
176
+ when 'RATIONAL'
177
+ value = to_rational(aLexeme, aFormat)
178
+ symb = 'INTEGER' if value.kind_of?(Integer)
173
179
  when 'REAL'
174
180
  value = to_real(aLexeme, aFormat)
175
181
  when 'STRING_LIT'
@@ -180,7 +186,7 @@ other literal data (section 2.4).
180
186
  value = aLexeme
181
187
  end
182
188
 
183
- return value
189
+ return [value, symb]
184
190
  end
185
191
 
186
192
  def to_boolean(aLexeme, aFormat)
@@ -196,6 +202,16 @@ other literal data (section 2.4).
196
202
  return value
197
203
  end
198
204
 
205
+ def to_rational(aLexeme, aFormat)
206
+ case aFormat
207
+ when :default
208
+ value = Rational(aLexeme)
209
+ value = value.numerator if value.denominator == 1
210
+ end
211
+
212
+ return value
213
+ end
214
+
199
215
  def to_real(aLexeme, aFormat)
200
216
  case aFormat
201
217
  when :default
@@ -1,3 +1,3 @@
1
1
  module Skeem
2
- VERSION = '0.2.09'.freeze
2
+ VERSION = '0.2.10'.freeze
3
3
  end
@@ -35,6 +35,15 @@ module Skeem
35
35
  ['+456', 456]
36
36
  ]
37
37
  end
38
+
39
+ let(:rational_tests) do
40
+ [
41
+ [-Rational(2,3), -Rational(2, 3)],
42
+ [Rational(22, 7), Rational(22, 7)],
43
+ ['-2/3', -Rational(2, 3)],
44
+ ['+22/7', Rational(22, 7)]
45
+ ]
46
+ end
38
47
 
39
48
  let(:real_tests) do
40
49
  [
@@ -83,6 +92,12 @@ module Skeem
83
92
  expect(subject.integer(literal)).to eq(predicted)
84
93
  end
85
94
  end
95
+
96
+ it 'should convert rational literals' do
97
+ rational_tests.each do |(literal, predicted)|
98
+ expect(subject.rational(literal)).to eq(predicted)
99
+ end
100
+ end
86
101
 
87
102
  it 'should convert real number literals' do
88
103
  real_tests.each do |(literal, predicted)|
@@ -163,6 +178,12 @@ module Skeem
163
178
  expect(subject.to_datum(literal)).to eq(predicted)
164
179
  end
165
180
  end
181
+
182
+ it 'should recognize & convert rational literals' do
183
+ rational_tests.each do |(literal, predicted)|
184
+ expect(subject.to_datum(literal)).to eq(predicted)
185
+ end
186
+ end
166
187
 
167
188
  it 'should recognize & convert real number literals' do
168
189
  real_tests.each do |(literal, predicted)|
@@ -825,6 +825,82 @@ SKEEM
825
825
  expect(result).to eq(expectation)
826
826
  end
827
827
  end
828
+
829
+ it 'should implement the floor-quotient procedure' do
830
+ checks = [
831
+ ['(floor-quotient 5 2)', 2],
832
+ ['(floor-quotient -5 2)', -3],
833
+ ['(floor-quotient 5 -2)', -3],
834
+ ['(floor-quotient -5 -2)', 2]
835
+ ]
836
+ checks.each do |(skeem_expr, expectation)|
837
+ result = subject.run(skeem_expr)
838
+ expect(result).to eq(expectation)
839
+ end
840
+ end
841
+
842
+ it 'should implement the floor-remainder (modulo) procedure' do
843
+ checks = [
844
+ ['(floor-remainder 16 4)', 0],
845
+ ['(floor-remainder 5 2)', 1],
846
+ ['(floor-remainder -45.0 7)', 4.0],
847
+ ['(floor-remainder 10.0 -3.0)', -2.0],
848
+ ['(floor-remainder -17 -9)', -8],
849
+ ['(modulo 16 4)', 0],
850
+ ['(modulo 5 2)', 1],
851
+ ['(modulo -45.0 7)', 4.0],
852
+ ['(modulo 10.0 -3.0)', -2.0],
853
+ ['(modulo -17 -9)', -8]
854
+ ]
855
+ checks.each do |(skeem_expr, expectation)|
856
+ result = subject.run(skeem_expr)
857
+ expect(result).to eq(expectation)
858
+ end
859
+ end
860
+
861
+ it 'should implement the truncate-quotient procedure' do
862
+ checks = [
863
+ ['(truncate-quotient 5 2)', 2],
864
+ ['(truncate-quotient -5 2)', -2],
865
+ ['(truncate-quotient 5 -2)', -2],
866
+ ['(truncate-quotient -5 -2)', 2],
867
+ ['(quotient 5 2)', 2],
868
+ ['(quotient -5 2)', -2],
869
+ ['(quotient 5 -2)', -2],
870
+ ['(quotient -5 -2)', 2]
871
+ ]
872
+ checks.each do |(skeem_expr, expectation)|
873
+ result = subject.run(skeem_expr)
874
+ expect(result).to eq(expectation)
875
+ end
876
+ end
877
+
878
+ it 'should implement the truncate-remainder procedure' do
879
+ checks = [
880
+ ['(truncate-remainder 5 2)', 1],
881
+ ['(truncate-remainder -5 2)', -1],
882
+ ['(truncate-remainder 5 -2)', 1],
883
+ ['(truncate-remainder -5 -2)', -1],
884
+ ['(remainder 5 2)', 1],
885
+ ['(remainder -5 2)', -1],
886
+ ['(remainder 5 -2)', 1],
887
+ ['(remainder -5 -2)', -1]
888
+ ]
889
+ checks.each do |(skeem_expr, expectation)|
890
+ result = subject.run(skeem_expr)
891
+ expect(result).to eq(expectation)
892
+ end
893
+ end
894
+
895
+ it 'should implement the test-equal procedure' do
896
+ checks = [
897
+ ["(test-equal (cons 1 2) (cons 1 2))", true]
898
+ ]
899
+ checks.each do |(skeem_expr, expectation)|
900
+ result = subject.run(skeem_expr)
901
+ expect(result).to eq(expectation)
902
+ end
903
+ end
828
904
  end # context
829
905
 
830
906
  context 'Second-order functions' do
@@ -35,6 +35,8 @@ SKEEM
35
35
  ['(+)', 0], # '+' as nullary operator. Example from section 6.2.6
36
36
  ['(+ -3)', -3], # '+' as unary operator
37
37
  ['(+ 3 4)', 7], # '+' as binary operator. Example from section 4.1.3
38
+ ['(+ 1/2 2/3)', Rational(7,6)],
39
+ ['(+ 1/2 3)', Rational(7,2)],
38
40
  ['(+ 2 2.34)', 4.34]
39
41
  ].each do |(expr, predicted)|
40
42
  result = subject.run(expr)
@@ -45,6 +47,7 @@ SKEEM
45
47
  it 'should implement the minus operator' do
46
48
  [
47
49
  ['(- 3)', -3], # '-' as unary operator (= sign change)
50
+ ['(- -2/3)', Rational(2, 3)],
48
51
  ['(- 3 4)', -1], # '-' as binary operator. Example from section 6.2.6
49
52
  ['(- 3 4 5)', -6] # '-' as variadic operator. Example from section 6.2.6
50
53
  ].each do |(expr, predicted)|
@@ -58,6 +61,7 @@ SKEEM
58
61
  ['(*)', 1], # '*' as nullary operator. Example from section 6.2.6
59
62
  ['(* 4)', 4], # '*' as unary operator. Example from section 6.2.6
60
63
  ['(* 5 8)', 40], # '*' as binary operator.
64
+ ['(* 2/3 5/7)', Rational(10, 21)],
61
65
  ['(* 2 3 4 5)', 120] # '*' as variadic operator.
62
66
  ].each do |(expr, predicted)|
63
67
  result = subject.run(expr)
@@ -67,28 +71,45 @@ SKEEM
67
71
 
68
72
  it 'should implement the division operator' do
69
73
  [
70
- ['(/ 3)', 1.0/3], # '/' as unary operator (= inverse of argument)
71
- ['(/ 3 4)', 3.0/4], # '/' as binary operator.
72
- ['(/ 3 4 5)', 3.0/20] # '/' as variadic operator. Example from section 6.2.6
74
+ ['(/ 3)', Rational(1, 3)], # '/' as unary operator (= inverse of argument)
75
+ ['(/ 3/4)', Rational(4, 3)],
76
+ ['(/ 3 4)', Rational(3, 4)], # '/' as binary operator.
77
+ ['(/ 2/3 5/7)', Rational(14, 15)],
78
+ ['(/ 3 4 5)', Rational(3, 20)] # '/' as variadic operator. Example from section 6.2.6
73
79
  ].each do |(expr, predicted)|
74
80
  result = subject.run(expr)
75
81
  expect(result).to eq(predicted)
76
82
  end
83
+
84
+ result = subject.run('(/ 3 4.5)')
85
+ expect(result.value).to be_within(0.000001).of(0.66666667)
77
86
  end
78
87
 
79
- it 'should implement the floor-remainder (modulo) procedure' do
88
+ it 'should implement the floor/ procedure' do
80
89
  checks = [
81
- ['(floor-remainder 16 4)', 0], # Binary procedure.
82
- ['(floor-remainder 5 2)', 1],
83
- ['(floor-remainder -45.0 7)', 4.0],
84
- ['(floor-remainder 10.0 -3.0)', -2.0],
85
- ['(floor-remainder -17 -9)', -8]
90
+ ['(floor/ 5 2)', [2, 1]], # Binary procedure.
91
+ ['(floor/ -5 2)', [-3, 1]],
92
+ ['(floor/ 5 -2)', [-3, -1]],
93
+ ['(floor/ -5 -2)', [2, -1]]
86
94
  ]
87
95
  checks.each do |(skeem_expr, expectation)|
88
96
  result = subject.run(skeem_expr)
89
- expect(result).to eq(expectation)
97
+ expect([result.car, result.cdr]).to eq(expectation)
90
98
  end
91
99
  end
100
+
101
+ it 'should implement the truncate/ procedure' do
102
+ checks = [
103
+ ['(truncate/ 5 2)', [2, 1]], # Binary procedure.
104
+ ['(truncate/ -5 2)', [-2, -1]],
105
+ ['(truncate/ 5 -2)', [-2, 1]],
106
+ ['(truncate/ -5 -2)', [2, -1]]
107
+ ]
108
+ checks.each do |(skeem_expr, expectation)|
109
+ result = subject.run(skeem_expr)
110
+ expect([result.car, result.cdr]).to eq(expectation)
111
+ end
112
+ end
92
113
  end # context
93
114
 
94
115
  context 'Comparison operators' do
@@ -228,12 +249,37 @@ SKEEM
228
249
  expect(result).to eq(expectation)
229
250
  end
230
251
  end
252
+
253
+ it 'should implement the max procedure' do
254
+ checks = [
255
+ ['(max 3 4)', 4],
256
+ ['(max 3.9 4)', 4],
257
+ ['(max 4 -7 2 0 -6)', 4]
258
+ ]
259
+ checks.each do |(skeem_expr, expectation)|
260
+ result = subject.run(skeem_expr)
261
+ expect(result).to eq(expectation)
262
+ end
263
+ end
264
+
265
+ it 'should implement the min procedure' do
266
+ checks = [
267
+ ['(min 3 4)', 3],
268
+ ['(min 3.9 4)', 3.9],
269
+ ['(min 4 -7 2 0 -6)', -7]
270
+ ]
271
+ checks.each do |(skeem_expr, expectation)|
272
+ result = subject.run(skeem_expr)
273
+ expect(result).to eq(expectation)
274
+ end
275
+ end
231
276
  end # context
232
277
 
233
278
  context 'Number procedures:' do
234
279
  it 'should implement the number? predicate' do
235
280
  checks = [
236
281
  ['(number? 3.1)', true],
282
+ ['(number? 22/7)', true],
237
283
  ['(number? 3)', true],
238
284
  ['(number? "3")', false],
239
285
  ['(number? #t)', false]
@@ -247,6 +293,7 @@ SKEEM
247
293
  it 'should implement the real? predicate' do
248
294
  checks = [
249
295
  ['(real? 3.1)', true],
296
+ ['(real? 22/7)', true],
250
297
  ['(real? 3)', true],
251
298
  ['(real? "3")', false],
252
299
  ['(real? #t)', false]
@@ -257,10 +304,26 @@ SKEEM
257
304
  end
258
305
  end
259
306
 
307
+ it 'should implement the rational? predicate' do
308
+ checks = [
309
+ ['(rational? 3.1)', false],
310
+ ['(rational? 3.0)', true],
311
+ ['(rational? 22/7)', true],
312
+ ['(rational? 3)', true],
313
+ ['(rational? "3")', false],
314
+ ['(rational? #t)', false]
315
+ ]
316
+ checks.each do |(skeem_expr, expectation)|
317
+ result = subject.run(skeem_expr)
318
+ expect(result).to eq(expectation)
319
+ end
320
+ end
321
+
260
322
  it 'should implement the integer? predicate' do
261
323
  checks = [
262
324
  ['(integer? 3.1)', false],
263
- # ['(integer? 3.0)', true], TODO: should pass when exact? will be implemented
325
+ ['(integer? 3.0)', true],
326
+ ['(integer? 22/7)', false],
264
327
  ['(integer? 3)', true],
265
328
  ['(integer? "3")', false],
266
329
  ['(integer? #t)', false]
@@ -274,6 +337,7 @@ SKEEM
274
337
  it 'should implement the number->string procedure' do
275
338
  checks = [
276
339
  ['(number->string 3.4)', '3.4'],
340
+ ['(number->string 22/7)', '22/7'],
277
341
  ['(number->string 1e2)', '100.0'],
278
342
  ['(number->string 1e-23)', '1.0e-23'],
279
343
  ['(number->string -7)', '-7']
@@ -82,6 +82,29 @@ module Skeem
82
82
  end
83
83
  end # context
84
84
 
85
+ context 'Rational literals recognition:' do
86
+ it 'should tokenize rational in default radix 10' do
87
+ tests = [
88
+ # couple [raw input, expected]
89
+ ['1/2', Rational(1, 2)],
90
+ ['-22/7', -Rational(22, 7)],
91
+ ]
92
+
93
+ tests.each do |(input, prediction)|
94
+ subject.reinitialize(input)
95
+ token = subject.tokens.first
96
+ expect(token.terminal).to eq('RATIONAL')
97
+ expect(token.lexeme).to eq(prediction)
98
+ end
99
+
100
+ # Special case: implicit promotion to integer
101
+ subject.reinitialize('8/4')
102
+ token = subject.tokens.first
103
+ expect(token.terminal).to eq('INTEGER')
104
+ expect(token.lexeme).to eq(2)
105
+ end
106
+ end # context
107
+
85
108
  context 'Real number recognition:' do
86
109
  it 'should tokenize real numbers' do
87
110
  tests = [
@@ -211,7 +234,7 @@ module Skeem
211
234
  expect(token.terminal).to eq('STRING_LIT')
212
235
  expect(token.lexeme).to eq('Second text')
213
236
  end
214
-
237
+
215
238
  it 'should cope with nested block comments' do
216
239
  input = '"First text" #| One #| Two |# comment #| Three |# |# "Second text"'
217
240
  subject.reinitialize(input)
@@ -223,7 +246,7 @@ module Skeem
223
246
  token = tokens[1]
224
247
  expect(token.terminal).to eq('STRING_LIT')
225
248
  expect(token.lexeme).to eq('Second text')
226
- end
249
+ end
227
250
  end
228
251
 
229
252
  context 'Scanning Scheme sample code' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skeem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.09
4
+ version: 0.2.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-10 00:00:00.000000000 Z
11
+ date: 2019-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley