skeem 0.2.04 → 0.2.05

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: 8efda1e2bcaf2e4d07ca4bc52f3708c0e217b27c191d6ed0a42d3b7125e26de5
4
- data.tar.gz: '0359bae57e376f724f17b0e10bcc819f6fe8ee1f2478d2b330111c16546ab8ec'
3
+ metadata.gz: f8c3a61e9162fe0df33e51f373614d56cac6e5bc9c4743a101b8805bad158843
4
+ data.tar.gz: 52feb4bc5edf9e9cf64089ef0e3fbb6cff2d917d964ecdf4d1e00d1d1409caed
5
5
  SHA512:
6
- metadata.gz: 48f64eea51bbbe3e701214101ad7bf114cf45ef4281db722de2b2786d0e932793cf6945bd2cfbb6afc8ea320264ca7eb920fc79d0f7a81ac49efd1cd9af863c1
7
- data.tar.gz: d4ae84b2ebf2d3a20efcbb950d11ea1fc2b31b06a62d33c5fd375df6ff726a0a0b51f2a693f8cb3d0a47864de59ed79f2fbafe2cad9e99092ce25f0784271944
6
+ metadata.gz: 1e99664327739a23130244b2cff9b81e9ad9be83773efc057cdb636f8642a3a1492a8de5ed0133857582c036228f27c22c5bf3e17949bd577f162b679c7aa090
7
+ data.tar.gz: 9014bdfed6b945a604ab55f940de00c9288773a0dd9a192825b5cd83e99d79c6434252e96289abbfe10b7f742ffa117034f767685c929b5dbefcb2d27e7c36d3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.2.05] - 2019-05-26
2
+ - Passing more standard Scheme tests, `append` procedure implemented.
3
+
4
+ ### Added
5
+ - File `primitive_builder.rb`: Added implementation for standard procedure `append`.
6
+ - Class `LambdaRep`: represents a parse tree of a lambda expression.
7
+
8
+ ### Changed
9
+ - File `grammar.rb`: Changed a grammar rule for (begin ...) expression because R7RS was too restrictive compared to main implementations.
10
+ - Class `SkmBuilder` several method updates (e.g. `reduce_alt_definition`)
11
+
12
+ ### Removed
13
+ - Class `Environment` superseded by class `SkmFrame`
14
+
1
15
  ## [0.2.04] - 2019-04-29
2
16
  - Support for local definitions [initial]
3
17
 
data/README.md CHANGED
@@ -221,7 +221,7 @@ This section lists the implemented standard procedures
221
221
  * Integer-level: `even?`, `odd?`
222
222
 
223
223
  #### List procedures
224
- * `list?`, `null?`, `pair?`, `car`, `cdr`, `caar`, `cadr`, `cdar`, `cddr`, `cons`, `length`, `list`, `list->vector`, `set-car!`, `set-cdr!`
224
+ * `list?`, `null?`, `pair?`, `append`, `car`, `cdr`, `caar`, `cadr`, `cdar`, `cddr`, `cons`, `length`, `list`, `list->vector`, `set-car!`, `set-cdr!`
225
225
 
226
226
  #### String procedures
227
227
  * `string?`, `string=?`, `string-append`, `string-length`, `string->symbol`
@@ -255,7 +255,7 @@ Here are a few other ones:
255
255
 
256
256
  - [rubic gem](https://rubygems.org/gems/rubic). The last commit for the [project](https://github.com/notozeki/rubic) is June 2015.
257
257
 
258
- - [RLisp](https://github.com/davydovanton/rlisp) ...Simple scheme interpreter written on ruby
258
+ - [RLisp](https://github.com/davydovanton/rlisp) ...Simple scheme interpreter written in ruby
259
259
  ## Contributing
260
260
 
261
261
  Bug reports and pull requests are welcome on GitHub at https://github.com/famished-tiger/Skeem.
data/lib/skeem/grammar.rb CHANGED
@@ -39,7 +39,7 @@ module Skeem
39
39
  rule 'expression' => 'procedure_call'
40
40
  rule 'expression' => 'lambda_expression'
41
41
  rule 'expression' => 'conditional'
42
- rule 'expression' => 'assignment'
42
+ rule 'expression' => 'assignment'
43
43
  rule 'expression' => 'derived_expression'
44
44
  rule 'literal' => 'quotation'
45
45
  rule 'literal' => 'self-evaluating'
@@ -97,7 +97,10 @@ module Skeem
97
97
  rule('assignment' => 'LPAREN SET! IDENTIFIER expression RPAREN').as 'assignment'
98
98
  rule('derived_expression' => 'LPAREN LET LPAREN binding_spec_star RPAREN body RPAREN').as 'short_let_form'
99
99
  rule('derived_expression' => 'LPAREN LET* LPAREN binding_spec_star RPAREN body RPAREN').as 'let_star_form'
100
- rule('derived_expression' => 'LPAREN BEGIN sequence RPAREN').as 'begin_expression'
100
+
101
+ # As the R7RS grammar is too restrictive,
102
+ # the next rule was made more general than its standard counterpart
103
+ rule('derived_expression' => 'LPAREN BEGIN body RPAREN').as 'begin_expression'
101
104
  rule 'derived_expression' => 'quasiquotation'
102
105
  rule('quasiquotation' => 'LPAREN QUASIQUOTE qq_template RPAREN').as 'quasiquotation'
103
106
  rule('quasiquotation' => 'GRAVE_ACCENT qq_template').as 'quasiquotation_short'
@@ -104,6 +104,7 @@ module Skeem
104
104
  create_cdr(aRuntime)
105
105
  create_length(aRuntime)
106
106
  create_list2vector(aRuntime)
107
+ create_append(aRuntime)
107
108
  create_setcar(aRuntime)
108
109
  create_setcdr(aRuntime)
109
110
  end
@@ -485,6 +486,44 @@ module Skeem
485
486
 
486
487
  define_primitive_proc(aRuntime, 'list->vector', unary, primitive)
487
488
  end
489
+
490
+ def create_append(aRuntime)
491
+ primitive = ->(runtime, arglist) do
492
+ if arglist.empty?
493
+ result = SkmEmptyList.instance
494
+ elsif arglist.size == 1
495
+ result = arglist[0]
496
+ else
497
+ parts = evaluate_arguments(arglist, aRuntime)
498
+ but_last = parts.take(parts.length - 1)
499
+ check_arguments(but_last, [SkmPair, SkmEmptyList], 'list', 'append')
500
+ result = parts.shift.klone # First list is taken
501
+ parts.each do |arg|
502
+ case arg
503
+ when SkmPair
504
+ cloned = arg.klone
505
+ if result.kind_of?(SkmEmptyList)
506
+ result = cloned
507
+ else
508
+ if result.kind_of?(SkmEmptyList)
509
+ result = SkmPair.new(arg, SkmEmptyList.instance)
510
+ else
511
+ result.append_list(cloned)
512
+ end
513
+ end
514
+ when SkmEmptyList
515
+ # Do nothing
516
+ else
517
+ result.append(arg)
518
+ end
519
+ end
520
+ end
521
+
522
+ result
523
+ end
524
+
525
+ define_primitive_proc(aRuntime, 'append', zero_or_more, primitive)
526
+ end
488
527
 
489
528
  def create_setcar(aRuntime)
490
529
  primitive = ->(runtime, pair_arg, obj_arg) do
@@ -682,6 +721,20 @@ module Skeem
682
721
  arglist.evaluate(aRuntime).to_a
683
722
  end
684
723
  end
724
+
725
+ def check_arguments(arguments, requiredRubyClass, requiredSkmType, aProcName)
726
+ arguments.each do |argument|
727
+ if requiredRubyClass.kind_of?(Array)
728
+ unless requiredRubyClass.include?(argument.class)
729
+ type_error(argument, requiredSkmType, aProcName)
730
+ end
731
+ else
732
+ unless argument.kind_of?(requiredRubyClass)
733
+ type_error(argument, requiredSkmType, aProcName)
734
+ end
735
+ end
736
+ end
737
+ end
685
738
 
686
739
  def check_argtype(argument, requiredRubyClass, requiredSkmType, aProcName)
687
740
  if requiredRubyClass.kind_of?(Array)
@@ -73,7 +73,7 @@ module Skeem
73
73
  # rule('definition' => 'LPAREN DEFINE LPAREN IDENTIFIER def_formals RPAREN body RPAREN').as 'alt_definition'
74
74
  # Equivalent to: (define IDENTIFIER (lambda (formals) body))
75
75
  def reduce_alt_definition(_production, aRange, _tokens, theChildren)
76
- lmbd = SkmLambda.new(aRange, theChildren[4], theChildren[6])
76
+ lmbd = SkmLambdaRep.new(aRange, theChildren[4], theChildren[6])
77
77
  # $stderr.puts lmbd.inspect
78
78
  SkmBinding.new(theChildren[3], lmbd)
79
79
  end
@@ -190,7 +190,7 @@ module Skeem
190
190
 
191
191
  # rule('lambda_expression' => 'LPAREN LAMBDA formals body RPAREN').as 'lambda_expression'
192
192
  def reduce_lambda_expression(_production, aRange, _tokens, theChildren)
193
- lmbd = SkmLambda.new(aRange, theChildren[2], theChildren[3])
193
+ lmbd = SkmLambdaRep.new(aRange, theChildren[2], theChildren[3])
194
194
  # $stderr.puts lmbd.inspect
195
195
  lmbd
196
196
  end
@@ -251,6 +251,7 @@ module Skeem
251
251
  # rule('sequence' => 'command_star expression').as 'sequence'
252
252
  def reduce_sequence(_production, _range, _tokens, theChildren)
253
253
  SkmPair.create_from_a(theChildren[0] << theChildren[1])
254
+
254
255
  end
255
256
 
256
257
  # rule('command_star' => 'command_star command').as 'multiple_commands'
@@ -283,9 +284,9 @@ module Skeem
283
284
  SkmBindingBlock.new(:let_star, theChildren[3], theChildren[5])
284
285
  end
285
286
 
286
- # rule('derived_expression' => 'LPAREN BEGIN sequence RPAREN').as 'begin_expression'
287
+ # rule('derived_expression' => 'LPAREN BEGIN body RPAREN').as 'begin_expression'
287
288
  def reduce_begin_expression(_production, aRange, _tokens, theChildren)
288
- SkmSequencingBlock.new(theChildren[2])
289
+ SkmSequencingBlock.new(theChildren[2])
289
290
  end
290
291
 
291
292
  # rule('quasiquotation' => 'LPAREN QUASIQUOTE qq_template RPAREN').as 'quasiquotation'
@@ -71,12 +71,12 @@ module Skeem
71
71
  # $stderr.puts "Parent environment #{aRuntime.environment.parent.object_id.to_s(16)}, "
72
72
  # $stderr.puts aRuntime.environment.inspect
73
73
  end
74
- # $stderr.puts ' operator: ' + operator.inspect
74
+ # $stderr.puts ' operator: ' + (operands.kind_of?(SkmLambda) ? "lambda #{object_id.to_s(16)}" : operator.inspect)
75
75
  # $stderr.puts ' original operands: ' + operands.inspect
76
- actuals = transform_operands(aRuntime)
77
- # $stderr.puts ' transformed operands: ' + actuals.inspect
78
76
  outcome, result = determine_callee(aRuntime)
79
77
  if outcome == :callee
78
+ actuals = transform_operands(aRuntime)
79
+ # $stderr.puts ' transformed operands: ' + actuals.inspect
80
80
  callee = result
81
81
  # if callee.kind_of?(SkmLambda)
82
82
  # aRuntime.push(callee.environment)
@@ -125,6 +125,8 @@ module Skeem
125
125
  # callee = fetch_callee(aRuntime, result)
126
126
  when Primitive::PrimitiveProcedure
127
127
  callee = operator
128
+ when SkmLambdaRep
129
+ callee = operator.evaluate(aRuntime)
128
130
  when SkmLambda
129
131
  callee = operator
130
132
  else
@@ -301,10 +303,11 @@ module Skeem
301
303
  end # class
302
304
 
303
305
 
304
-
305
- require_relative 'skm_procedure_exec'
306
-
307
- class SkmLambda < SkmMultiExpression
306
+ # Parse tree representation of a Lambda
307
+ # - Not bound to a frame (aka environment)
308
+ # - Knows the parse representation of its embedded definitions
309
+ # - Knows the parse representation of the body
310
+ class SkmLambdaRep < SkmMultiExpression
308
311
  include DatumDSL
309
312
 
310
313
  # @!attribute [r] formals
@@ -312,7 +315,6 @@ require_relative 'skm_procedure_exec'
312
315
  attr_reader :formals
313
316
  attr_reader :definitions
314
317
  attr_reader :sequence
315
- attr_reader :environment
316
318
 
317
319
  def initialize(aPosition, theFormals, aBody)
318
320
  super(aPosition)
@@ -321,6 +323,158 @@ require_relative 'skm_procedure_exec'
321
323
  @sequence = aBody[:sequence]
322
324
  end
323
325
 
326
+ def evaluate(aRuntime)
327
+ SkmLambda.new(self, aRuntime)
328
+ end
329
+
330
+ def callable?
331
+ true
332
+ end
333
+
334
+ def call(aRuntime, theActuals)
335
+ set_cond_environment(aRuntime.environment) # Last chance for anonymous lambda
336
+ application = SkmProcedureExec.new(self)
337
+ application.run!(aRuntime, theActuals)
338
+ end
339
+
340
+ def arity
341
+ formals.arity
342
+ end
343
+
344
+ def required_arity
345
+ formals.required_arity
346
+ end
347
+
348
+ alias eqv? equal?
349
+ alias skm_equal? equal?
350
+
351
+ def bound!(aFrame)
352
+ set_cond_environment(aFrame)
353
+ end
354
+
355
+ def inspect
356
+ result = inspect_prefix + "@object_id=#{object_id.to_s(16)}, "
357
+ result << inspect_specific
358
+ result << inspect_suffix
359
+ result
360
+ end
361
+
362
+ def associations
363
+ [:formals, :definitions, :sequence]
364
+ end
365
+
366
+ def bind_locals(aRuntime, theActuals)
367
+ actuals = theActuals
368
+ count_actuals = actuals.size
369
+
370
+ if (count_actuals < required_arity) ||
371
+ ((count_actuals > required_arity) && !formals.variadic?)
372
+ # $stderr.puts "Error"
373
+ # $stderr.puts self.inspect
374
+ raise StandardError, msg_arity_mismatch(theActuals)
375
+ end
376
+ return if count_actuals.zero? && !formals.variadic?
377
+ bind_required_locals(aRuntime, theActuals)
378
+ if formals.variadic?
379
+ variadic_part_raw = actuals.drop(required_arity)
380
+ variadic_part = variadic_part_raw.map do |actual|
381
+ case actual
382
+ when ProcedureCall
383
+ actual.evaluate(aRuntime)
384
+ when SkmQuotation
385
+ actual.evaluate(aRuntime)
386
+ else
387
+ to_datum(actual)
388
+ end
389
+ end
390
+ variadic_arg_name = formals.formals.last
391
+ args_coll = SkmPair.create_from_a(variadic_part)
392
+ a_def = SkmBinding.new(variadic_arg_name, args_coll)
393
+ a_def.evaluate(aRuntime)
394
+ aRuntime.add_binding(a_def.variable, a_def.value)
395
+ # $stderr.puts "Tef #{a_def.inspect}"
396
+ # $stderr.puts "Tef #{actuals.inspect}"
397
+ # $stderr.puts "Tef #{variadic_part.inspect}"
398
+ # $stderr.puts "Tef #{aProcedureCall.inspect}"
399
+ # a_def.evaluate(aRuntime)
400
+ end
401
+ #aProcedureCall.operands_consumed = true
402
+ end
403
+
404
+ private
405
+
406
+ # Purpose: bind each formal from lambda to an actual value from the call
407
+ def bind_required_locals(aRuntime, theActuals)
408
+ max_index = required_arity - 1
409
+ actuals = theActuals
410
+ formal_names = formals.formals.map(&:value)
411
+
412
+ formals.formals.each_with_index do |arg_name, index|
413
+ arg = actuals[index]
414
+ if arg.nil?
415
+ if actuals.empty? && formals.variadic?
416
+ arg = SkmPair.create_from_a([])
417
+ else
418
+ raise StandardError, "Unbound variable: '#{arg_name.value}'"
419
+ end
420
+ end
421
+
422
+ # IMPORTANT: execute procedure call in argument list now
423
+ arg = arg.evaluate(aRuntime) if arg.kind_of?(ProcedureCall)
424
+ unless arg.kind_of?(SkmElement)
425
+ arg = to_datum(arg)
426
+ end
427
+ # a_def = SkmDefinition.new(position, arg_name, arg)
428
+ a_def = SkmBinding.new(arg_name, arg)
429
+ # $stderr.puts "Lambda #{object_id.to_s(16)}"
430
+ # $stderr.puts "LOCAL #{arg_name.value} #{arg.inspect}"
431
+ if arg.kind_of?(SkmVariableReference) && !formal_names.include?(arg.value)
432
+ aRuntime.add_binding(arg_name, a_def)
433
+ else
434
+ aRuntime.add_binding(a_def.variable, a_def.evaluate(aRuntime))
435
+ end
436
+ break if index >= max_index
437
+ end
438
+ end
439
+
440
+ def msg_arity_mismatch(actuals)
441
+ # *** ERROR: wrong number of arguments for #<closure morph> (required 2, got 1)
442
+ msg1 = "Wrong number of arguments for procedure "
443
+ count_actuals = actuals.size
444
+ msg2 = "(required #{required_arity}, got #{count_actuals})"
445
+ msg1 + msg2
446
+ end
447
+
448
+ def inspect_specific
449
+ result = ''
450
+ result << '@formals ' + formals.inspect + ', '
451
+ result << '@definitions ' + definitions.inspect + ', '
452
+ result << '@sequence ' + sequence.inspect + inspect_suffix
453
+
454
+ result
455
+ end
456
+ end # class
457
+
458
+
459
+
460
+
461
+ require 'forwardable'
462
+ require_relative 'skm_procedure_exec'
463
+
464
+ class SkmLambda < SkmMultiExpression
465
+ include DatumDSL
466
+ extend Forwardable
467
+
468
+ attr_reader :representation
469
+ attr_reader :environment
470
+
471
+ def_delegators(:@representation, :formals, :definitions, :sequence)
472
+
473
+ def initialize(aRepresentation, aRuntime)
474
+ @representation = aRepresentation
475
+ @environment = aRuntime.environment
476
+ end
477
+
324
478
  def evaluate(aRuntime)
325
479
  self
326
480
  end
@@ -429,7 +583,7 @@ require_relative 'skm_procedure_exec'
429
583
 
430
584
  result
431
585
  end
432
-
586
+
433
587
  def dup_cond(aRuntime)
434
588
  if environment
435
589
  result = self
@@ -438,17 +592,17 @@ require_relative 'skm_procedure_exec'
438
592
  twin.set_cond_environment(aRuntime.environment)
439
593
  result = twin
440
594
  end
441
-
595
+
442
596
  result
443
597
  end
444
-
598
+
445
599
  def doppelganger(aRuntime)
446
600
  twin = self.dup
447
601
  twin.set_cond_environment(aRuntime.environment.dup)
448
602
  result = twin
449
-
603
+
450
604
  result
451
- end
605
+ end
452
606
 
453
607
  def set_cond_environment(theFrame)
454
608
  # $stderr.puts "Lambda #{object_id.to_s(16)}, env [#{environment.object_id.to_s(16)}]"
@@ -2,7 +2,7 @@ require 'singleton'
2
2
  require_relative 'skm_element'
3
3
 
4
4
  module Skeem
5
- # From R7RS: The empty list is a special object of its own type.
5
+ # From R7RS: The empty list is a special object of its own type.
6
6
  # It is not a pair, it has no elements, and its length is zero.
7
7
  class SkmEmptyList < SkmElement
8
8
  include Singleton
@@ -14,30 +14,38 @@ module Skeem
14
14
  def null?
15
15
  true
16
16
  end
17
-
17
+
18
18
  def pair?
19
19
  false
20
20
  end
21
-
21
+
22
22
  def length
23
23
  0
24
24
  end
25
-
25
+
26
26
  def empty?
27
27
  true
28
28
  end
29
-
29
+
30
30
  def verbatim?
31
31
  true
32
32
  end
33
-
33
+
34
34
  def skm_equal?(other)
35
35
  equal?(other)
36
36
  end
37
-
37
+
38
38
  def to_a
39
39
  []
40
- end
40
+ end
41
+
42
+ def klone
43
+ self
44
+ end
45
+
46
+ def append_list(aList)
47
+ aList
48
+ end
41
49
 
42
50
  def evaluate(_runtime)
43
51
  self
@@ -14,6 +14,15 @@ module Skeem
14
14
  @cdr = tail
15
15
  end
16
16
 
17
+ def klone
18
+ if cdr.kind_of?(SkmPair)
19
+ new_cdr = cdr.klone
20
+ else
21
+ new_cdr = cdr
22
+ end
23
+ self.class.new(car, new_cdr)
24
+ end
25
+
17
26
  # Construct new instance with car and cdr respectively equal to
18
27
  # car.evaluate and cdr.evaluate
19
28
  def clone_evaluate(aRuntime)
@@ -82,6 +91,14 @@ module Skeem
82
91
  result
83
92
  end
84
93
 
94
+ def last_pair
95
+ if cdr.nil? || (cdr == SkmEmptyList.instance) || (!cdr.kind_of?(SkmPair))
96
+ self
97
+ else
98
+ cdr.last_pair
99
+ end
100
+ end
101
+
85
102
  def last
86
103
  self.to_a.last
87
104
  end
@@ -116,15 +133,23 @@ module Skeem
116
133
  end
117
134
 
118
135
  def append(anElement)
119
- if cdr.nil? || cdr.kind_of?(SkmEmptyList)
120
- self.cdr = SkmPair.new(anElement, SkmEmptyList.instance)
121
- elsif cdr.kind_of?(SkmPair)
122
- self.cdr.append(anElement)
136
+ last_one = self.last_pair
137
+ if last_one.cdr.nil? || last_one.cdr.kind_of?(SkmEmptyList)
138
+ last_one.cdr = SkmPair.new(anElement, SkmEmptyList.instance)
123
139
  else
124
140
  raise StandardError, "Cannot append #{anElement.inspect}"
125
141
  end
126
142
  end
127
143
 
144
+ def append_list(aList)
145
+ last_one = self.last_pair
146
+ if last_one.cdr.nil? || last_one.cdr.kind_of?(SkmEmptyList)
147
+ last_one.cdr = aList
148
+ else
149
+ raise StandardError, "Cannot append list #{aList.inspect}"
150
+ end
151
+ end
152
+
128
153
  def evaluate(aRuntime)
129
154
  return SkmEmptyList.instance if empty?
130
155
  if car.kind_of?(SkmIdentifier) && car.is_var_name
@@ -19,9 +19,11 @@ module Skeem
19
19
  runtime.push(frame)
20
20
  definition.bind_locals(runtime, theActuals)
21
21
  evaluate_defs(aRuntime)
22
- # definition.evaluate_defs(runtime)
23
22
  # $stderr.puts "Locals"
24
- # $stderr.puts frame.bindings.keys.join(', ')
23
+ # frame.bindings.each_pair do |key, elem|
24
+ # $stderr.print key.to_s + ' => '
25
+ # $stderr.puts elem.kind_of?(SkmLambda) ? " Lambda = #{elem.object_id.to_s(16)}" : elem.inspect
26
+ # end
25
27
  result = definition.evaluate_sequence(runtime)
26
28
  runtime.pop
27
29
  # $stderr.puts "Lambda result: #{result.object_id.to_s(16)}" if result.kind_of?(SkmLambda)
@@ -33,8 +35,8 @@ module Skeem
33
35
 
34
36
  def evaluate_defs(aRuntime)
35
37
  definition.definitions.each do |bndng|
38
+ val = bndng.value.evaluate(aRuntime)
36
39
  var = bndng.variable.evaluate(aRuntime)
37
- val = bndng.value.evaluate(aRuntime)
38
40
  frame.add_binding(var, val)
39
41
  end
40
42
  end
@@ -160,7 +160,7 @@ module Skeem
160
160
 
161
161
  # Sequencing construct
162
162
  class SkmSequencingBlock < SkmUnaryExpression
163
- alias sequence child
163
+ alias sequence child # Can be a body
164
164
 
165
165
  def initialize(aSequence)
166
166
  super(nil, aSequence)
@@ -168,26 +168,73 @@ module Skeem
168
168
 
169
169
  def evaluate(aRuntime)
170
170
  result = nil
171
- if sequence
172
- sequence.kind_of?(SkmPair)
173
- sequence.to_a.each do |cmd|
174
- begin
175
- if cmd.kind_of?(SkmLambda)
176
- result = cmd.dup_cond(aRuntime)
177
- else
178
- result = cmd.evaluate(aRuntime)
179
- end
180
- rescue NoMethodError => exc
181
- $stderr.puts self.inspect
182
- $stderr.puts sequence.inspect
183
- $stderr.puts cmd.inspect
184
- raise exc
171
+ return result if sequence.nil?
172
+
173
+ case sequence
174
+ when SkmPair
175
+ result = eval_pair(aRuntime)
176
+ when Hash
177
+ result = eval_body(aRuntime)
178
+ else
179
+ result = sequence.evaluate(aRuntime)
180
+ end
181
+
182
+ result
183
+ end
184
+
185
+ private
186
+
187
+ def eval_pair(aRuntime)
188
+ result = nil
189
+ 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
201
+ end
202
+ end
203
+
204
+ result
205
+ end
206
+
207
+ def eval_body(aRuntime)
208
+ result = nil
209
+ aRuntime.push(SkmFrame.new(aRuntime.environment))
210
+
211
+ unless sequence[:defs].empty?
212
+ sequence[:defs].each do |dfn|
213
+ dfn.evaluate(aRuntime)
214
+ end
215
+ end
216
+
217
+ if sequence[:sequence].kind_of?(SkmPair)
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)
185
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
186
230
  end
187
- elsif
188
- result = sequence.evaluate(aRuntime)
189
231
  end
232
+ elsif
233
+ result = sequence.evaluate(aRuntime)
234
+ end
190
235
 
236
+ aRuntime.pop
237
+
191
238
  result
192
239
  end
193
240
  end # class
data/lib/skeem/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Skeem
2
- VERSION = '0.2.04'.freeze
2
+ VERSION = '0.2.05'.freeze
3
3
  end
@@ -304,20 +304,21 @@ SKEEM
304
304
  result = subject.run(source)
305
305
  expect(result.last.last.value).to eq(4)
306
306
  end
307
-
308
-
307
+
309
308
  it 'should support the nested define construct' do
310
309
  source = <<-SKEEM
311
310
  (define (quadruple x)
312
311
  (define (double x) ; define a local procedure double
313
312
  (+ x x))
314
313
  (double (double x))) ; nested calls to the local procedure
315
-
314
+
316
315
  (quadruple 5) ; => 20
317
316
  SKEEM
318
317
  result = subject.run(source)
319
- expect(result.last.value).to eq(20)
318
+ expect(result.last.value).to eq(20)
320
319
  end
320
+
321
+
321
322
  end # context
322
323
 
323
324
  context 'Binding constructs:' do
@@ -376,8 +377,6 @@ SKEEM
376
377
  expect(result.last).to eq(3)
377
378
  end
378
379
 
379
-
380
-
381
380
  it 'should implement let* expression' do
382
381
  source = <<-SKEEM
383
382
  (let ((x 2) (y 3))
@@ -411,6 +410,21 @@ SKEEM
411
410
  result = subject.run(source)
412
411
  expect(result).to eq(7)
413
412
  end
413
+
414
+ it 'should support begin as lambda body' do
415
+ source = <<-SKEEM
416
+ (define kube (lambda (x)
417
+ (begin
418
+ (define z x)
419
+ (* z z z)
420
+ )
421
+ ))
422
+ (kube 3)
423
+ (kube 4)
424
+ SKEEM
425
+ result = subject.run(source)
426
+ expect(result.last).to eq(64)
427
+ end
414
428
  end # context
415
429
 
416
430
  context 'Quasiquotation:' do
@@ -452,7 +452,10 @@ SKEEM
452
452
  ['(list? #f)', false],
453
453
  ['(list? 1)', false],
454
454
  ['(list? "bar")', false],
455
+ ["(list? 'a)", false],
456
+ ["(list? '(a))", true],
455
457
  ["(list? '(1 2 3))", true],
458
+ ["(list? '(3 . 4))", false],
456
459
  ["(list? '())", true]
457
460
  ]
458
461
  checks.each do |(skeem_expr, expectation)|
@@ -563,6 +566,36 @@ SKEEM
563
566
  end
564
567
  end
565
568
 
569
+ def array2list_ids(arr)
570
+ arr.map { |elem| SkmIdentifier.create(elem) }
571
+ end
572
+
573
+ it 'should implement the append procedure' do
574
+ checks = [
575
+ ["(append '(a b c) '())", array2list_ids(['a', 'b', 'c'])],
576
+ ["(append '() '(a b c))", array2list_ids(['a', 'b', 'c'])],
577
+ ["(append '(x) '(y))", array2list_ids(['x', 'y'])],
578
+ ["(append '(a) '(b c d))", array2list_ids(['a', 'b', 'c', 'd'])],
579
+ ["(append '(a b) '(c d))", array2list_ids(['a', 'b', 'c', 'd'])],
580
+ ["(append '(a b) '(c) 'd)", array2list_ids(['a', 'b', 'c', 'd'])],
581
+ ["(append '(a (b)) '((c)))", [SkmIdentifier.create('a'),
582
+ SkmPair.create_from_a(array2list_ids(['b'])),
583
+ SkmPair.create_from_a(array2list_ids(['c']))]]
584
+ ]
585
+ checks.each do |(skeem_expr, expectation)|
586
+ result = subject.run(skeem_expr)
587
+ expect(result.to_a).to eq(expectation)
588
+ end
589
+ end
590
+
591
+ it 'should implement the procedure for an improper list' do
592
+ result = subject.run("(append '(a b) '(c . d))")
593
+ expect(result.car).to eq( SkmIdentifier.create('a'))
594
+ expect(result.cdr.car).to eq( SkmIdentifier.create('b'))
595
+ expect(result.cdr.cdr.car).to eq( SkmIdentifier.create('c'))
596
+ expect(result.cdr.cdr.cdr).to eq( SkmIdentifier.create('d'))
597
+ end
598
+
566
599
  it 'should implement the list->vector procedure' do
567
600
  checks = [
568
601
  ["(list->vector '())", []],
@@ -71,18 +71,18 @@ module Skeem
71
71
  end # context
72
72
  end # describe
73
73
 
74
- describe SkmLambda do
74
+ describe SkmLambdaRep do
75
75
  let(:pos) { double('fake-position') }
76
76
  let(:s_formals) { double('fake-formals') }
77
77
  let(:s_defs) { double('fake-definitions') }
78
78
  let(:s_sequence) { double('fake-sequence') }
79
79
  let(:s_body) do { defs: s_defs, sequence: s_sequence } end
80
80
 
81
- subject { SkmLambda.new(pos, s_formals, s_body) }
81
+ subject { SkmLambdaRep.new(pos, s_formals, s_body) }
82
82
 
83
83
  context 'Initialization:' do
84
84
  it 'should be initialized with a pos and 3 expressions' do
85
- expect{ SkmLambda.new(pos, s_formals, s_body) }.not_to raise_error
85
+ expect{ SkmLambdaRep.new(pos, s_formals, s_body) }.not_to raise_error
86
86
  end
87
87
 
88
88
  it 'should know its formals' do
@@ -100,7 +100,7 @@ module Skeem
100
100
 
101
101
  context 'Provided services:' do
102
102
  it 'should return its text representation' do
103
- txt1 = '<Skeem::SkmLambda: @formals #<Double "fake-formals">, '
103
+ txt1 = '<Skeem::SkmLambdaRep: @formals #<Double "fake-formals">, '
104
104
  txt2 = '@definitions #<Double "fake-definitions">, '
105
105
  txt3 = '@sequence #<Double "fake-sequence">>>'
106
106
  # Remove "unpredictable" part of actual text
@@ -34,11 +34,37 @@ module Skeem
34
34
  end # context
35
35
 
36
36
  context 'Provided services:' do
37
- let(:runtime) { Runtime.new(Environment.new) }
37
+ let(:runtime) { Runtime.new(SkmFrame.new) }
38
38
  let(:list_length_2) { SkmPair.new(integer(10), subject) }
39
39
  let(:quirk_element) { double('three') }
40
40
  let(:quirk_members) { [integer(10), quirk_element] }
41
41
 
42
+ it 'should clone itself' do
43
+ cloned = subject.klone
44
+ expect(cloned.car).to eq(subject.car)
45
+ expect(cloned.cdr).to eq(subject.cdr)
46
+ end
47
+
48
+ it 'should clone a proper list' do
49
+ pair2 = SkmPair.new(identifier('b'), SkmEmptyList.instance)
50
+ pair1 = SkmPair.new(identifier('a'), pair2)
51
+
52
+ cloned = pair1.klone
53
+ expect(cloned.car).to eq(identifier('a'))
54
+ expect(cloned.cdr.car).to eq(identifier('b'))
55
+ expect(cloned.cdr.cdr).to be_null
56
+ end
57
+
58
+ it 'should clone a improper list' do
59
+ pair2 = SkmPair.new(identifier('b'), identifier('c'))
60
+ pair1 = SkmPair.new(identifier('a'), pair2)
61
+
62
+ cloned = pair1.klone
63
+ expect(cloned.car).to eq(identifier('a'))
64
+ expect(cloned.cdr.car).to eq(identifier('b'))
65
+ expect(cloned.cdr.cdr).to eq(identifier('c'))
66
+ end
67
+
42
68
  it 'should know its length' do
43
69
  expect(subject.length).to eq(1)
44
70
 
@@ -88,6 +114,21 @@ module Skeem
88
114
  expect(list_length_2.to_a).to eq([integer(10), sample_car])
89
115
  end
90
116
 
117
+ it 'should return the last pair of a proper list' do
118
+ expect(subject.last_pair).to eq(subject)
119
+
120
+ pair2 = SkmPair.new(identifier('b'), SkmEmptyList.instance)
121
+ pair1 = SkmPair.new(identifier('a'), pair2)
122
+ expect(pair1.last_pair).to eq(pair1)
123
+ end
124
+
125
+ it 'should return the last pair of an improper list' do
126
+ pair3 = SkmPair.new(identifier('c'), identifier('d'))
127
+ pair2 = SkmPair.new(identifier('b'), pair3)
128
+ pair1 = SkmPair.new(identifier('a'), pair2)
129
+ expect(pair1.last_pair).to eq(pair3)
130
+ end
131
+
91
132
  it 'should return the last element of a list' do
92
133
  expect(subject.last).to eq(sample_car)
93
134
  expect(list_length_2.last).to eq(sample_car)
@@ -1,6 +1,5 @@
1
1
  require_relative '../spec_helper' # Use the RSpec framework
2
2
  require_relative '../../lib/skeem/datum_dsl'
3
- require_relative '../../lib/skeem/environment'
4
3
  require_relative '../../lib/skeem/s_expr_nodes'
5
4
  require_relative '../../lib/skeem/skm_unary_expression' # Load the classes under test
6
5
 
@@ -49,7 +48,7 @@ module Skeem
49
48
  end # context
50
49
 
51
50
  context 'Provided services:' do
52
- let(:runtime) { Runtime.new(Environment.new) }
51
+ let(:runtime) { Runtime.new(SkmFrame.new) }
53
52
 
54
53
  # it 'should return the child(datum) at evaluation' do
55
54
  # expect(subject.evaluate(runtime)).to be_equal(subject.child)
@@ -90,7 +89,7 @@ module Skeem
90
89
  end # context
91
90
 
92
91
  context 'Provided services:' do
93
- let(:runtime) { Runtime.new(Environment.new) }
92
+ let(:runtime) { Runtime.new(SkmFrame.new) }
94
93
 
95
94
  it 'should return the child(template) at evaluation' do
96
95
  expect(subject.evaluate(runtime)).to be_equal(subject.child)
@@ -131,7 +130,7 @@ module Skeem
131
130
  end # context
132
131
 
133
132
  context 'Provided services:' do
134
- let(:runtime) { Runtime.new(Environment.new) }
133
+ let(:runtime) { Runtime.new(SkmFrame.new) }
135
134
 
136
135
  it 'should return the child(template) at evaluation' do
137
136
  expect(subject.evaluate(runtime)).to be_equal(subject.child)
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.04
4
+ version: 0.2.05
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-04-29 00:00:00.000000000 Z
11
+ date: 2019-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -86,7 +86,6 @@ files:
86
86
  - lib/skeem.rb
87
87
  - lib/skeem/datum_dsl.rb
88
88
  - lib/skeem/element_visitor.rb
89
- - lib/skeem/environment.rb
90
89
  - lib/skeem/grammar.rb
91
90
  - lib/skeem/interpreter.rb
92
91
  - lib/skeem/parser.rb
@@ -111,7 +110,6 @@ files:
111
110
  - skeem.gemspec
112
111
  - spec/skeem/datum_dsl_spec.rb
113
112
  - spec/skeem/element_visitor_spec.rb
114
- - spec/skeem/environment_spec.rb
115
113
  - spec/skeem/interpreter_spec.rb
116
114
  - spec/skeem/lambda_spec.rb
117
115
  - spec/skeem/parser_spec.rb
@@ -158,7 +156,6 @@ summary: Skeem is an interpreter of a subset of the Scheme programming language.
158
156
  test_files:
159
157
  - spec/skeem/datum_dsl_spec.rb
160
158
  - spec/skeem/element_visitor_spec.rb
161
- - spec/skeem/environment_spec.rb
162
159
  - spec/skeem/interpreter_spec.rb
163
160
  - spec/skeem/lambda_spec.rb
164
161
  - spec/skeem/parser_spec.rb
@@ -1,80 +0,0 @@
1
- module Skeem
2
- class Environment
3
- attr_reader :outer
4
-
5
- attr_reader(:bindings)
6
-
7
- def initialize(outerEnv = nil)
8
- @bindings = {}
9
- @outer = outerEnv
10
- end
11
-
12
- def define(anIdentifier, anExpression)
13
- raise StandardError, anIdentifier unless anIdentifier.kind_of?(String)
14
- @bindings[anIdentifier] = anExpression
15
- end
16
-
17
- def fetch(anIdentifier)
18
- found = bindings[anIdentifier]
19
- if found.nil? && outer
20
- found = outer.fetch(anIdentifier)
21
- end
22
-
23
- found
24
- end
25
-
26
- def empty?
27
- my_result = bindings.empty?
28
- if my_result && outer
29
- my_result = outer.empty?
30
- end
31
-
32
- my_result
33
- end
34
-
35
- def size
36
- my_result = bindings.size
37
- my_result += outer.size if outer
38
-
39
- my_result
40
- end
41
-
42
- def include?(anIdentifier)
43
- my_result = bindings.include?(anIdentifier)
44
- if my_result == false && outer
45
- my_result = outer.include?(anIdentifier)
46
- end
47
-
48
- my_result
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
64
-
65
- def inspect
66
- result = ''
67
- if outer
68
- result << outer.inspect
69
- else
70
- return "\n"
71
- end
72
- result << "\n----\n"
73
- bindings.each_pair do |key, expr|
74
- result << "#{key.inspect} => #{expr.inspect}\n"
75
- end
76
-
77
- result
78
- end
79
- end # class
80
- end # module
@@ -1,81 +0,0 @@
1
- require_relative '../spec_helper' # Use the RSpec framework
2
- require_relative '../../lib/skeem/environment' # Load the class under test
3
-
4
- module Skeem
5
- describe Environment do
6
- let(:sample_env) { Environment.new }
7
- context 'Initialization:' do
8
- it 'could be initialized without argument' do
9
- expect { Environment.new() }.not_to raise_error
10
- end
11
-
12
- it 'could be initialized with optional argument' do
13
- expect { Environment.new(sample_env) }.not_to raise_error
14
- end
15
-
16
- it 'should have no default bindings' do
17
- expect(subject).to be_empty
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
26
- end # context
27
-
28
- context 'Provided services:' do
29
- it 'should add entries' do
30
- entry = double('dummy')
31
- subject.define('dummy', entry)
32
- expect(subject.size).to eq(1)
33
- expect(subject.bindings['dummy']).not_to be_nil
34
- expect(subject.bindings['dummy']).to eq(entry)
35
- end
36
-
37
- it 'should know whether it is empty' do
38
- # Case 1: no entry
39
- expect(subject.empty?).to be_truthy
40
-
41
- # Case 2: existing entry
42
- entry = double('dummy')
43
- subject.define('dummy', entry)
44
- expect(subject.empty?).to be_falsey
45
-
46
- # Case 3: entry defined in outer environment
47
- nested = Environment.new(subject)
48
- expect(nested.empty?).to be_falsey
49
- end
50
-
51
- it 'should retrieve entries' do
52
- # Case 1: non-existing entry
53
- expect(subject.fetch('dummy')).to be_nil
54
-
55
- # Case 2: existing entry
56
- entry = double('dummy')
57
- subject.define('dummy', entry)
58
- expect(subject.fetch('dummy')).to eq(entry)
59
-
60
- # Case 3: entry defined in outer environment
61
- nested = Environment.new(subject)
62
- expect(nested.fetch('dummy')).to eq(entry)
63
- end
64
-
65
- it 'should know the total number of bindings' do
66
- # Case 1: non-existing entry
67
- expect(subject.size).to be_zero
68
-
69
- # Case 2: existing entry
70
- entry = double('dummy')
71
- subject.define('dummy', entry)
72
- expect(subject.size).to eq(1)
73
-
74
- # Case 3: entry defined in outer environment
75
- nested = Environment.new(subject)
76
- expect(nested.size).to eq(1)
77
- end
78
- end # context
79
-
80
- end # describe
81
- end # module