skeem 0.2.09 → 0.2.10

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