skeem 0.0.15 → 0.0.16

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: 17793fa7c6e636874b0d7fe8515d745b7ff664af
4
- data.tar.gz: f24f83ad056e1af1ecac866e2daa649c94e544e6
3
+ metadata.gz: 8b34c77d5c2d4f0893f69e79e8973b4c6eca3481
4
+ data.tar.gz: f10a360e77925612bd6c95bb5e160417ba538983
5
5
  SHA512:
6
- metadata.gz: 64d9feba80f8ac945161c326a8783cdd34babf4f46bbec94020192ad7643a408be059c72e922bf452387dff253708d2b855d7adbac93b9bb9fff0cf4f354bbce
7
- data.tar.gz: 00f745fc5abeaa2f1f6d83007b21bc0cb7a4fbc3974d79f71be89c19324857a543339328d406a04ca5dd130e36c789c163e3b982c3be87b879b1978fbb277492
6
+ metadata.gz: da4f16148a354cb6ee8553d46e204ed97e27a88a117727a1c6a18f8441b5ed08a93fe56db11ade0fc903e934b60002315a98a70ef9b1a83d5015f7246f27ab4a
7
+ data.tar.gz: c75610b85fa5fa4a56c66832e19e193b401118dce728c5dacd755f5d570d3898939a11c2aa46ba83b9d1a6cde23d1a9e865657957dccc565e90c02c8806499aa
@@ -1,3 +1,18 @@
1
+ ## [0.0.16] - 2018-10-06
2
+ - Added built-in procedures `odd?`, `even?`, `square`, and `floor-remainder` (`modulo`).
3
+ - Supports procedures without argument.
4
+ - Implements second syntax form for variable definition.
5
+ - Fixed nasty bug when same variable name used in nested procedure calls.
6
+
7
+ ### Added
8
+ - Method `Environment#depth to count the nesting levels
9
+ - File `primitive_builder.rb` implementation of: `odd?`, `even?`, `square`, `floor-remainder`, `modulo` procedures.
10
+ - File `grammar.rb` rule for second syntax form for variable definition.
11
+ - File `grammar.rb` rule for calling procedures without argument.
12
+
13
+ ### Fixed
14
+ - Method `SkmDefinition#evaluate` Infinite recursion when a variable, say x, referred to a variable with same name in outer scope.
15
+
1
16
  ## [0.0.15] - 2018-09-30
2
17
  Recursive functions are now supported.
3
18
  Interpreter pre-loads a Scheme file with standard procedures (zero?, positive?, negative?, abs)
@@ -47,5 +47,19 @@ module Skeem
47
47
 
48
48
  my_result
49
49
  end
50
+
51
+ # The number of outer parents the current environment has.
52
+ # @return [Integer] The nesting levels
53
+ def depth
54
+ count = 0
55
+
56
+ curr_env = self
57
+ while curr_env.outer
58
+ count += 1
59
+ curr_env = curr_env.outer
60
+ end
61
+
62
+ count
63
+ end
50
64
  end # class
51
65
  end # module
@@ -27,6 +27,7 @@ module Skeem
27
27
  rule 'cmd_or_def' => 'definition'
28
28
  rule 'command' => 'expression'
29
29
  rule('definition' => 'LPAREN DEFINE IDENTIFIER expression RPAREN').as 'definition'
30
+ rule('definition' => 'LPAREN DEFINE LPAREN IDENTIFIER def_formals RPAREN body RPAREN').as 'alt_definition'
30
31
  rule('expression' => 'IDENTIFIER').as 'variable_reference'
31
32
  rule 'expression' => 'literal'
32
33
  rule 'expression' => 'procedure_call'
@@ -36,13 +37,15 @@ module Skeem
36
37
  rule 'self-evaluating' => 'BOOLEAN'
37
38
  rule 'self-evaluating' => 'number'
38
39
  rule 'self-evaluating' => 'STRING_LIT'
39
- rule 'procedure_call' => 'LPAREN operator RPAREN'
40
+ rule('procedure_call' => 'LPAREN operator RPAREN').as 'proc_call_nullary'
40
41
  rule('procedure_call' => 'LPAREN operator operand_plus RPAREN').as 'proc_call_args'
41
42
  rule('operand_plus' => 'operand_plus operand').as 'multiple_operands'
42
43
  rule('operand_plus' => 'operand').as 'last_operand'
43
44
  rule 'operator' => 'expression'
44
45
  rule 'operand' => 'expression'
45
46
  rule('lambda_expression' => 'LPAREN LAMBDA formals body RPAREN').as 'lambda_expression'
47
+ rule 'def_formals' => 'identifier_star'
48
+ # rule('def_formals' => 'identifier_star period identifier').as 'dotted_formals'
46
49
  rule('formals' => 'LPAREN identifier_star RPAREN').as 'identifiers_as_formals'
47
50
  rule 'formals' => 'IDENTIFIER'
48
51
  rule 'formals' => 'LPAREN identifier_plus PERIOD IDENTIFIER RPAREN'
@@ -12,6 +12,8 @@ module Skeem
12
12
  add_boolean_procedures(aRuntime)
13
13
  add_string_procedures(aRuntime)
14
14
  add_symbol_procedures(aRuntime)
15
+ add_output_procedures(aRuntime)
16
+ add_special_procedures(aRuntime)
15
17
  end
16
18
 
17
19
  private
@@ -21,6 +23,7 @@ module Skeem
21
23
  def_procedure(aRuntime, create_minus)
22
24
  def_procedure(aRuntime, create_multiply)
23
25
  def_procedure(aRuntime, create_divide)
26
+ def_procedure(aRuntime, create_modulo)
24
27
  end
25
28
 
26
29
  def add_comparison(aRuntime)
@@ -50,6 +53,14 @@ module Skeem
50
53
  def_procedure(aRuntime, create_symbol?)
51
54
  end
52
55
 
56
+ def add_output_procedures(aRuntime)
57
+ def_procedure(aRuntime, create_newline)
58
+ end
59
+
60
+ def add_special_procedures(aRuntime)
61
+ def_procedure(aRuntime, create_debug)
62
+ end
63
+
53
64
  def create_plus()
54
65
  plus_code = ->(runtime, arglist) do
55
66
  first_one = arglist.head.evaluate(runtime)
@@ -102,6 +113,20 @@ module Skeem
102
113
  ['/', divide_code]
103
114
  end
104
115
 
116
+
117
+ def create_modulo()
118
+ modulo_code = ->(runtime, arglist) do
119
+ operand_1 = arglist.head.evaluate(runtime)
120
+ operands = arglist.tail.to_eval_enum(runtime)
121
+ operand_2 = operands.first.evaluate(runtime)
122
+ raw_result = operand_1.value.modulo(operand_2.value)
123
+ to_skm(raw_result)
124
+ end
125
+
126
+ ['floor-remainder', modulo_code, 'modulo', modulo_code]
127
+ end
128
+
129
+
105
130
  def create_equal
106
131
  equal_code = ->(runtime, arglist) do
107
132
  first_one = arglist.head.evaluate(runtime)
@@ -232,9 +257,29 @@ module Skeem
232
257
  ['symbol?', pred_code]
233
258
  end
234
259
 
235
- def def_procedure(aRuntime, aPair)
236
- func = PrimitiveProcedure.new(aPair.first, aPair.last)
237
- define(aRuntime, func.identifier, func)
260
+ def create_newline
261
+ nl_code = ->(runtime, arg) do
262
+ # @TODO: make output stream configurable
263
+ print "\n"
264
+ end
265
+
266
+ ['newline', nl_code]
267
+ end
268
+
269
+ def create_debug
270
+ debug_code = ->(runtime, arg) do
271
+ require 'debug'
272
+ end
273
+
274
+ ['debug', debug_code]
275
+ end
276
+
277
+
278
+ def def_procedure(aRuntime, pairs)
279
+ pairs.each_slice(2) do |(name, code)|
280
+ func = PrimitiveProcedure.new(name, code)
281
+ define(aRuntime, func.identifier, func)
282
+ end
238
283
  end
239
284
 
240
285
  def define(aRuntime, aKey, anEntry)
@@ -24,6 +24,21 @@ module Skeem
24
24
  environment.bindings.clear
25
25
  @environment = environment.outer
26
26
  end
27
+
28
+ def depth()
29
+ return environment.depth
30
+ end
31
+
32
+ # Make the outer enviromnent thecurrent one inside the provided block
33
+ def pop
34
+ env = environment
35
+ @environment = environment.outer
36
+ env
37
+ end
38
+
39
+ def push(anEnvironment)
40
+ @environment = anEnvironment
41
+ end
27
42
 
28
43
  private
29
44
 
@@ -61,11 +61,23 @@ module Skeem
61
61
  def reduce_definition(_production, aRange, _tokens, theChildren)
62
62
  SkmDefinition.new(aRange, theChildren[2], theChildren[3])
63
63
  end
64
+
65
+ # rule('definition' => 'LPAREN DEFINE LPAREN IDENTIFIER def_formals RPAREN body RPAREN').as 'alt_definition'
66
+ # Equivalent to: (define IDENTIFIER (lambda (formals) body))
67
+ def reduce_alt_definition(_production, aRange, _tokens, theChildren)
68
+ lmbd = SkmLambda.new(aRange, theChildren[4], theChildren[6])
69
+ SkmDefinition.new(aRange, theChildren[3], lmbd)
70
+ end
64
71
 
65
72
  # rule('expression' => 'IDENTIFIER').as 'variable_reference'
66
73
  def reduce_variable_reference(_production, aRange, _tokens, theChildren)
67
74
  SkmVariableReference.new(aRange, theChildren[0])
68
75
  end
76
+
77
+ # rule('procedure_call' => 'LPAREN operator RPAREN').as 'proc_call_nullary'
78
+ def reduce_proc_call_nullary(_production, aRange, _tokens, theChildren)
79
+ ProcedureCall.new(aRange, theChildren[1], [])
80
+ end
69
81
 
70
82
  # rule('proc_call_args' => 'LPAREN operator operand_plus RPAREN')
71
83
  def reduce_proc_call_args(_production, aRange, _tokens, theChildren)
@@ -83,10 +95,18 @@ module Skeem
83
95
  end
84
96
 
85
97
  # rule('lambda_expression' => 'LPAREN LAMBDA formals body RPAREN').as 'lambda_expression'
86
- def reduce_lambda_expression(_production, _range, _tokens, theChildren)
87
- lmbd = SkmLambda.new(_range, theChildren[2], theChildren[3])
98
+ def reduce_lambda_expression(_production, aRange, _tokens, theChildren)
99
+ lmbd = SkmLambda.new(aRange, theChildren[2], theChildren[3])
100
+ # $stderr.puts lmbd.inspect
101
+ lmbd
88
102
  end
89
103
 
104
+ # rule('def_formals' => 'identifier_star period identifier').as 'dotted_formals'
105
+ def reduce_last_operand(_production, _range, _tokens, theChildren)
106
+ [theChildren.last]
107
+ end
108
+
109
+
90
110
  # rule('formals' => 'LPAREN identifier_star RPAREN').as 'identifiers_as_formals'
91
111
  def reduce_identifiers_as_formals(_production, _range, _tokens, theChildren)
92
112
  theChildren[1]
@@ -200,6 +200,7 @@ module Skeem
200
200
  # Construct an Enumerator that will return iteratively the result
201
201
  # of 'evaluate' method of each members of self.
202
202
  def to_eval_enum(aRuntime)
203
+ =begin
203
204
  elements = self.members
204
205
 
205
206
  new_enum = Enumerator.new do |result|
@@ -208,6 +209,8 @@ module Skeem
208
209
  end
209
210
 
210
211
  new_enum
212
+ =end
213
+ members.map { |elem| elem.evaluate(aRuntime) }
211
214
  end
212
215
 
213
216
  # Part of the 'visitee' role in Visitor design pattern.
@@ -245,9 +248,29 @@ module Skeem
245
248
 
246
249
  def evaluate(aRuntime)
247
250
  var_key = variable.evaluate(aRuntime)
248
- aRuntime.define(var_key, self)
249
- expression.evaluate(aRuntime) if expression.kind_of?(SkmLambda)
250
- self
251
+ aRuntime.define(var_key, self)
252
+ case expression
253
+ when SkmLambda
254
+ result = expression.evaluate(aRuntime)
255
+
256
+ when SkmVariableReference
257
+ other_key = expression.variable.evaluate(aRuntime)
258
+ if var_key.value != other_key.value
259
+ result = expression.evaluate(aRuntime)
260
+ else
261
+ # INFINITE LOOP DANGER: definition of 'x' is a reference to 'x'!
262
+ # Way out: the lookup for the reference should start from outer
263
+ # environment.
264
+ env = aRuntime.pop
265
+ @expression = expression.evaluate(aRuntime)
266
+ aRuntime.push(env)
267
+ result = expression
268
+ end
269
+ else
270
+ result = self
271
+ end
272
+
273
+ result
251
274
  end
252
275
 
253
276
  # call method should only invoked when the expression is a SkmLambda
@@ -325,10 +348,10 @@ module Skeem
325
348
  raise err, err_msg
326
349
  end
327
350
  procedure = aRuntime.environment.fetch(var_key.value)
328
- # puts "## CALL(#{var_key.value}) ###################"
329
- # puts operands.inspect
351
+ # $stderr.puts "## CALL(#{var_key.value}) ###################"
352
+ # $stderr.puts operands.inspect
330
353
  result = procedure.call(aRuntime, self)
331
- # puts "## RETURN #{result.inspect}"
354
+ # $stderr.puts "## RETURN #{result.inspect}"
332
355
  result
333
356
  end
334
357
 
@@ -394,7 +417,7 @@ module Skeem
394
417
  aRuntime.nest
395
418
  bind_locals(aRuntime, aProcedureCall)
396
419
  # TODO remove next line
397
- # puts aRuntime.environment.inspect
420
+ # $stderr.puts aRuntime.environment.inspect
398
421
  result = evaluate_defs(aRuntime)
399
422
  result = evaluate_sequence(aRuntime)
400
423
  aRuntime.unnest
@@ -418,12 +441,12 @@ module Skeem
418
441
  if arg.nil?
419
442
  raise StandardError, "Unbound variable: '#{arg_name.value}'"
420
443
  end
421
-
444
+
422
445
  # IMPORTANT: execute procedure call in argument list now
423
446
  arg = arg.evaluate(aRuntime) if arg.kind_of?(ProcedureCall)
424
447
  a_def = SkmDefinition.new(position, arg_name, arg)
425
448
  a_def.evaluate(aRuntime)
426
- # puts "LOCAL #{a_def.inspect}"
449
+ # $stderr.puts "LOCAL #{a_def.inspect}"
427
450
  end
428
451
  end
429
452
 
@@ -1,25 +1,41 @@
1
1
  ; Standard R7RS procedures from section 6.2.6
2
- (define zero?
2
+ (define zero?
3
3
  (lambda (z)
4
4
  (if (= z 0)
5
5
  #t
6
6
  #f)))
7
7
 
8
8
  ; Return true if x greater or equal to zero; false otherwise
9
- (define positive?
9
+ (define positive?
10
10
  (lambda (x)
11
11
  (if (>= x 0)
12
12
  #t
13
13
  #f)))
14
14
 
15
- (define negative?
15
+ (define negative?
16
16
  (lambda (x)
17
17
  (if (< x 0)
18
18
  #t
19
19
  #f)))
20
-
21
- (define abs
20
+
21
+ (define odd?
22
+ (lambda (n)
23
+ (if (= (modulo n 2) 1)
24
+ #t
25
+ #f)))
26
+
27
+ (define even?
28
+ (lambda (n)
29
+ (if (= (modulo n 2) 0)
30
+ #t
31
+ #f)))
32
+
33
+ (define abs
22
34
  (lambda (x)
23
- (if (>= x 0)
35
+ (if (positive? x)
24
36
  x
25
- (- x))))
37
+ (- x))))
38
+
39
+ (define square
40
+ (lambda (z)
41
+ (* z z)))
@@ -1,3 +1,3 @@
1
1
  module Skeem
2
- VERSION = '0.0.15'.freeze
2
+ VERSION = '0.0.16'.freeze
3
3
  end
@@ -3,14 +3,26 @@ require_relative '../../lib/skeem/environment' # Load the class under test
3
3
 
4
4
  module Skeem
5
5
  describe Environment do
6
+ let(:sample_env) { Environment.new }
6
7
  context 'Initialization:' do
7
- it 'should be initialized with opional argument' do
8
+ it 'could be initialized without argument' do
8
9
  expect { Environment.new() }.not_to raise_error
9
10
  end
11
+
12
+ it 'could be initialized with optional argument' do
13
+ expect { Environment.new(sample_env) }.not_to raise_error
14
+ end
10
15
 
11
16
  it 'should have no default bindings' do
12
17
  expect(subject).to be_empty
13
18
  end
19
+
20
+ it 'should have depth of zero or one' do
21
+ expect(subject.depth).to be_zero
22
+
23
+ instance = Environment.new(sample_env)
24
+ expect(instance.depth).to eq(1)
25
+ end
14
26
  end # context
15
27
 
16
28
  context 'Provided services:' do
@@ -1,3 +1,4 @@
1
+ require 'stringio'
1
2
  require_relative '../spec_helper' # Use the RSpec framework
2
3
  require_relative '../../lib/skeem/interpreter' # Load the class under test
3
4
 
@@ -128,8 +129,9 @@ SKEEM
128
129
  it 'should implement the lambda function with one arg' do
129
130
  source = <<-SKEEM
130
131
  ; Simplified 'abs' function implementation
131
- (define abs (lambda(x)
132
- (if (< x 0) (- x) x)))
132
+ (define abs
133
+ (lambda (x)
134
+ (if (< x 0) (- x) x)))
133
135
  SKEEM
134
136
  subject.run(source)
135
137
  result = subject.run('(abs -3)')
@@ -143,8 +145,9 @@ SKEEM
143
145
  it 'should implement the lambda function with two args' do
144
146
  source = <<-SKEEM
145
147
  ; Simplified 'min' function implementation
146
- (define min (lambda(x y)
147
- (if (< x y) x y)))
148
+ (define min
149
+ (lambda (x y)
150
+ (if (< x y) x y)))
148
151
  SKEEM
149
152
  subject.run(source)
150
153
  result = subject.run('(min 1 2)')
@@ -159,12 +162,25 @@ SKEEM
159
162
  source = <<-SKEEM
160
163
  ; Example from R7RS section 4.1.5
161
164
  (define fact (lambda (n)
162
- (if (<= n 1) 1 (* n (fact (- n 1))))))
165
+ (if (<= n 1)
166
+ 1
167
+ (* n (fact (- n 1))))))
163
168
  (fact 10)
164
169
  SKEEM
165
170
  result = subject.run(source)
166
171
  expect(result.value).to eq(3628800)
167
172
  end
173
+
174
+ it 'should implement the compact define + lambda syntax' do
175
+ source = <<-SKEEM
176
+ ; Alternative syntax to: (define f (lambda x (+ x 42)))
177
+ (define (f x)
178
+ (+ x 42))
179
+ (f 23)
180
+ SKEEM
181
+ result = subject.run(source)
182
+ expect(result.value).to eq(65)
183
+ end
168
184
  end # context
169
185
 
170
186
  context 'Built-in primitive procedures' do
@@ -218,6 +234,20 @@ SKEEM
218
234
  expect(result).to be_kind_of(SkmInteger)
219
235
  expect(result.value).to eq(210)
220
236
  end
237
+
238
+ it 'should implement the floor-remainder (modulo) procedure' do
239
+ checks = [
240
+ ['(modulo 16 4)', 0],
241
+ ['(modulo 5 2)', 1],
242
+ ['(modulo -45.0 7)', 4.0],
243
+ ['(modulo 10.0 -3.0)', -2.0],
244
+ ['(modulo -17 -9)', -8]
245
+ ]
246
+ checks.each do |(skeem_expr, expectation)|
247
+ result = subject.run(skeem_expr)
248
+ expect(result.value).to eq(expectation)
249
+ end
250
+ end
221
251
 
222
252
  it 'should implement the equality operator' do
223
253
  checks = [
@@ -376,6 +406,14 @@ SKEEM
376
406
  expect(result.value).to eq(expectation)
377
407
  end
378
408
  end
409
+
410
+ it 'should implement the newline procedure' do
411
+ default_stdout = $stdout
412
+ $stdout = StringIO.new()
413
+ subject.run('(newline) (newline) (newline)')
414
+ expect($stdout.string).to match(/\n\n\n$/)
415
+ $stdout = default_stdout
416
+ end
379
417
  end # context
380
418
 
381
419
  context 'Built-in standard procedures' do
@@ -439,6 +477,32 @@ SKEEM
439
477
  end
440
478
  end
441
479
 
480
+ it 'should implement the even? predicate' do
481
+ checks = [
482
+ ['(even? 0)', true],
483
+ ['(even? 1)', false],
484
+ ['(even? 2.0)', true],
485
+ ['(even? -120762398465)', false]
486
+ ]
487
+ checks.each do |(skeem_expr, expectation)|
488
+ result = subject.run(skeem_expr)
489
+ expect(result.value).to eq(expectation)
490
+ end
491
+ end
492
+
493
+ it 'should implement the odd? predicate' do
494
+ checks = [
495
+ ['(odd? 0)', false],
496
+ ['(odd? 1)', true],
497
+ ['(odd? 2.0)', false],
498
+ ['(odd? -120762398465)', true]
499
+ ]
500
+ checks.each do |(skeem_expr, expectation)|
501
+ result = subject.run(skeem_expr)
502
+ expect(result.value).to eq(expectation)
503
+ end
504
+ end
505
+
442
506
  it 'should implement the abs function' do
443
507
  checks = [
444
508
  ['(abs 3.1)', 3.1],
@@ -452,7 +516,19 @@ SKEEM
452
516
  result = subject.run(skeem_expr)
453
517
  expect(result.value).to eq(expectation)
454
518
  end
455
- end
519
+ end
520
+
521
+ it 'should implement the square function' do
522
+ checks = [
523
+ ['(square 42)', 1764],
524
+ ['(square 2.0)', 4.0],
525
+ ['(square -7)', 49]
526
+ ]
527
+ checks.each do |(skeem_expr, expectation)|
528
+ result = subject.run(skeem_expr)
529
+ expect(result.value).to eq(expectation)
530
+ end
531
+ end
456
532
  end # context
457
533
  end # describe
458
534
  end # module
@@ -32,17 +32,24 @@ module Skeem
32
32
  end
33
33
 
34
34
  it 'should add nested environment' do
35
+ expect(subject.depth).to be_zero
35
36
  env_before = subject.environment
36
37
  subject.nest
38
+
37
39
  expect(subject.environment).not_to eq(env_before)
38
40
  expect(subject.environment.outer).to eq(env_before)
41
+ expect(subject.depth).to eq(1)
39
42
  end
40
43
 
41
44
  it 'should remove nested environment' do
45
+ expect(subject.depth).to be_zero
42
46
  subject.nest
43
47
  outer_before = subject.environment.outer
48
+ expect(subject.depth).to eq(1)
49
+
44
50
  subject.unnest
45
51
  expect(subject.environment).to eq(outer_before)
52
+ expect(subject.depth).to be_zero
46
53
  end
47
54
  end # context
48
55
  end # describe
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.0.15
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-30 00:00:00.000000000 Z
11
+ date: 2018-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley