skeem 0.2.04 → 0.2.05

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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