skeem 0.0.15 → 0.0.16

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