skeem 0.0.17 → 0.0.18

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.
@@ -0,0 +1,111 @@
1
+ require_relative '../s_expr_nodes'
2
+
3
+ module Skeem
4
+ module Primitive
5
+ class PrimitiveProcedure
6
+ attr_reader(:identifier)
7
+ attr_reader(:arity)
8
+ attr_reader(:code)
9
+
10
+ # param [anArity] Arity of the lambda code (ignoring the runtime object)
11
+ def initialize(anId, anArity, aRubyLambda)
12
+ @identifier = anId.kind_of?(String) ? SkmIdentifier.create(anId) : anId
13
+ @code = code_validated(aRubyLambda)
14
+ @arity = arity_validated(anArity)
15
+ end
16
+
17
+ # Arguments are positional in a primitive procedure.
18
+ def call(aRuntime, aProcedureCall)
19
+ check_actual_count(aProcedureCall)
20
+ do_call(aRuntime, aProcedureCall.operands.to_a)
21
+ end
22
+
23
+ private
24
+
25
+ def code_validated(aRubyLambda)
26
+ unless aRubyLambda.lambda?
27
+ error_lambda('must be implemented with a Ruby lambda.')
28
+ end
29
+ if aRubyLambda.parameters.size.zero?
30
+ error_lambda('lambda takes no parameter.')
31
+ end
32
+
33
+ aRubyLambda
34
+ end
35
+
36
+ def arity_validated(anArity)
37
+ count_param = code.parameters.size
38
+
39
+ if anArity.variadic?
40
+ if (anArity.low + 2) != count_param
41
+ discrepancy_arity_argument_count(anArity.low, count_param, 2)
42
+ end
43
+ else # fixed arity...
44
+ if (anArity.high + 1) != count_param
45
+ discrepancy_arity_argument_count(anArity.high, count_param, 1)
46
+ end
47
+ end
48
+
49
+ anArity
50
+ end
51
+
52
+ def check_actual_count(aProcedureCall)
53
+ count_actuals = aProcedureCall.operands.size
54
+ if arity.nullary?
55
+ unless count_actuals.zero?
56
+ wrong_number_arguments(arity.high, count_actuals)
57
+ end
58
+ elsif arity.variadic?
59
+ if count_actuals < arity.low
60
+ wrong_number_arguments(arity.low, count_actuals)
61
+ end
62
+ else # fixed non-zero arity...
63
+ if count_actuals != arity.high
64
+ wrong_number_arguments(arity.high, count_actuals)
65
+ end
66
+ end
67
+ end
68
+
69
+ def do_call(aRuntime, operands)
70
+ if arity.nullary?
71
+ result = code.call(aRuntime)
72
+ elsif arity.variadic?
73
+ if arity.low.zero?
74
+ result = code.call(aRuntime, operands)
75
+ else
76
+ arguments = []
77
+ arguments << operands.take(arity.low).flatten
78
+ count_delta = operands.size - arity.low
79
+ arguments << SkmList.new(operands.slice(-count_delta, count_delta))
80
+ #p operands.size
81
+ #p count_delta
82
+ #p arguments.inspect
83
+ result = code.send(:call, aRuntime, *arguments.flatten)
84
+ end
85
+ else
86
+ result = code.send(:call, aRuntime, *operands)
87
+ end
88
+
89
+ result
90
+ end
91
+
92
+ def error_lambda(message_suffix)
93
+ msg1 = "Primitive procedure '#{identifier.value}'"
94
+ raise StandardError, msg1 + ' ' + message_suffix
95
+ end
96
+
97
+ def discrepancy_arity_argument_count(arity_required, count_param, delta)
98
+ msg1 = "Discrepancy in primitive procedure '#{identifier.value}'"
99
+ msg2 = "between arity (#{arity_required}) + #{delta}"
100
+ msg3 = "and parameter count of lambda #{count_param}."
101
+ raise StandardError, msg1 + ' ' + msg2 + ' ' + msg3
102
+ end
103
+
104
+ def wrong_number_arguments(required, actual)
105
+ msg1 = "Wrong number of arguments for #<Procedure #{identifier.value}>"
106
+ msg2 = "(required at least #{required}, got #{actual})"
107
+ raise StandardError, msg1 + ' ' + msg2
108
+ end
109
+ end # class
110
+ end # module
111
+ end # module
@@ -61,7 +61,7 @@ module Skeem
61
61
  def reduce_definition(_production, aRange, _tokens, theChildren)
62
62
  SkmDefinition.new(aRange, theChildren[2], theChildren[3])
63
63
  end
64
-
64
+
65
65
  # rule('definition' => 'LPAREN DEFINE LPAREN IDENTIFIER def_formals RPAREN body RPAREN').as 'alt_definition'
66
66
  # Equivalent to: (define IDENTIFIER (lambda (formals) body))
67
67
  def reduce_alt_definition(_production, aRange, _tokens, theChildren)
@@ -73,7 +73,7 @@ module Skeem
73
73
  def reduce_variable_reference(_production, aRange, _tokens, theChildren)
74
74
  SkmVariableReference.new(aRange, theChildren[0])
75
75
  end
76
-
76
+
77
77
  # rule('procedure_call' => 'LPAREN operator RPAREN').as 'proc_call_nullary'
78
78
  def reduce_proc_call_nullary(_production, aRange, _tokens, theChildren)
79
79
  ProcedureCall.new(aRange, theChildren[1], [])
@@ -89,6 +89,11 @@ module Skeem
89
89
  theChildren[0] << theChildren[1]
90
90
  end
91
91
 
92
+ # rule('operand_plus' => 'operand').as 'last_operand'
93
+ def reduce_last_operand(_production, _range, _tokens, theChildren)
94
+ [theChildren.last]
95
+ end
96
+
92
97
  # rule('operand_plus' => 'operand').as 'last_operand'
93
98
  def reduce_last_operand(_production, _range, _tokens, theChildren)
94
99
  [theChildren.last]
@@ -99,18 +104,23 @@ module Skeem
99
104
  lmbd = SkmLambda.new(aRange, theChildren[2], theChildren[3])
100
105
  # $stderr.puts lmbd.inspect
101
106
  lmbd
107
+ end
108
+
109
+ # rule('formals' => 'LPAREN identifier_star RPAREN').as 'fixed_arity_formals'
110
+ def reduce_fixed_arity_formals(_production, _range, _tokens, theChildren)
111
+ SkmFormals.new(theChildren[1], :fixed)
112
+ end
113
+
114
+ # rule('formals' => 'IDENTIFIER').as 'variadic_formals'
115
+ def reduce_variadic_formals(_production, _range, _tokens, theChildren)
116
+ SkmFormals.new([theChildren[0]], :variadic)
102
117
  end
103
118
 
104
- # rule('def_formals' => 'identifier_star period identifier').as 'dotted_formals'
105
- def reduce_last_operand(_production, _range, _tokens, theChildren)
106
- [theChildren.last]
119
+ # rule('formals' => 'LPAREN identifier_plus PERIOD IDENTIFIER RPAREN').as 'dotted_formals'
120
+ def reduce_dotted_formals(_production, _range, _tokens, theChildren)
121
+ formals = theChildren[1] << theChildren[3]
122
+ SkmFormals.new(formals, :variadic)
107
123
  end
108
-
109
-
110
- # rule('formals' => 'LPAREN identifier_star RPAREN').as 'identifiers_as_formals'
111
- def reduce_identifiers_as_formals(_production, _range, _tokens, theChildren)
112
- theChildren[1]
113
- end
114
124
 
115
125
  # rule('identifier_star' => 'identifier_star IDENTIFIER').as 'identifier_star'
116
126
  def reduce_identifier_star(_production, _range, _tokens, theChildren)
@@ -121,6 +131,16 @@ module Skeem
121
131
  def reduce_no_identifier_yet(_production, _range, _tokens, theChildren)
122
132
  []
123
133
  end
134
+
135
+ # rule('identifier_plus' => 'identifier_plus IDENTIFIER').as 'multiple_identifiers'
136
+ def reduce_multiple_identifiers(_production, _range, _tokens, theChildren)
137
+ theChildren[0] << theChildren[1]
138
+ end
139
+
140
+ # rule('identifier_plus' => 'IDENTIFIER').as 'last_identifier'
141
+ def reduce_last_identifier(_production, _range, _tokens, theChildren)
142
+ [theChildren[0]]
143
+ end
124
144
 
125
145
  # rule('body' => 'definition_star sequence').as 'body'
126
146
  def reduce_body(_production, _range, _tokens, theChildren)
@@ -171,7 +171,7 @@ module Skeem
171
171
  attr_accessor(:members)
172
172
  extend Forwardable
173
173
 
174
- def_delegators :@members, :each, :first, :length, :empty?
174
+ def_delegators :@members, :each, :first, :last, :length, :empty?, :size
175
175
 
176
176
  def initialize(theMembers)
177
177
  super(nil)
@@ -187,13 +187,8 @@ module Skeem
187
187
  end
188
188
 
189
189
  def evaluate(aRuntime)
190
- result = nil
191
-
192
- members.each do |elem|
193
- result = elem.evaluate(aRuntime)
194
- end
195
-
196
- result
190
+ list_evaluated = members.map { |elem| elem.evaluate(aRuntime) }
191
+ SkmList.new(list_evaluated)
197
192
  end
198
193
 
199
194
  # Factory method.
@@ -233,6 +228,7 @@ module Skeem
233
228
 
234
229
  alias children members
235
230
  alias subnodes members
231
+ alias to_a members
236
232
  alias rest tail
237
233
  end # class
238
234
 
@@ -248,11 +244,11 @@ module Skeem
248
244
 
249
245
  def evaluate(aRuntime)
250
246
  var_key = variable.evaluate(aRuntime)
251
- aRuntime.define(var_key, self)
247
+ aRuntime.define(var_key, self)
252
248
  case expression
253
249
  when SkmLambda
254
- result = expression.evaluate(aRuntime)
255
-
250
+ result = expression.evaluate(aRuntime)
251
+
256
252
  when SkmVariableReference
257
253
  other_key = expression.variable.evaluate(aRuntime)
258
254
  if var_key.value != other_key.value
@@ -399,6 +395,75 @@ module Skeem
399
395
  end
400
396
  end # class
401
397
 
398
+ SkmArity = Struct.new(:low, :high) do
399
+ def nullary?
400
+ low.zero? && high == 0
401
+ end
402
+
403
+ def variadic?
404
+ high == '*'
405
+ end
406
+
407
+ def ==(other)
408
+ return true if self.object_id == other.object_id
409
+ result = false
410
+
411
+ case other
412
+ when SkmArity
413
+ result = true if (low == other.low) && (high == other.high)
414
+ when Array
415
+ result = true if (low == other.first) && (high == other.last)
416
+ when Integer
417
+ result = true if (low == other) && (high == other)
418
+ end
419
+
420
+ result
421
+ end
422
+ end
423
+
424
+ class SkmFormals
425
+ attr_reader :formals
426
+ attr_reader :arity
427
+
428
+ # @param arityKind [Symbol] One of the following: :fixed, :variadic
429
+ def initialize(theFormals, arityKind)
430
+ @formals = theFormals
431
+ arity_set(arityKind)
432
+ end
433
+
434
+ def evaluate(aRuntime)
435
+ formals.map! { |param| param.evaluate(aRuntime) }
436
+ end
437
+
438
+ def nullary?
439
+ arity.nullary?
440
+ end
441
+
442
+ def variadic?
443
+ arity.variadic?
444
+ end
445
+
446
+ def required_arity
447
+ (arity.high == '*') ? arity.low : arity.high
448
+ end
449
+
450
+ private
451
+
452
+ def arity_set(arityKind)
453
+ fixed_arity = formals.size
454
+
455
+ if arityKind == :fixed
456
+ @arity = SkmArity.new(fixed_arity, fixed_arity)
457
+ else # :variadic
458
+ if formals.empty?
459
+ raise StandardError, 'Internal error: inconsistent arity'
460
+ else
461
+ @arity = SkmArity.new(fixed_arity - 1, '*')
462
+ end
463
+ end
464
+ end
465
+ end # class
466
+
402
467
  class SkmLambda < SkmElement
403
468
  # @!attribute [r] formals
404
469
  # @return [Array<SkmIdentifier>] the argument names
@@ -414,7 +479,7 @@ module Skeem
414
479
  end
415
480
 
416
481
  def evaluate(aRuntime)
417
- formals.map! { |param| param.evaluate(aRuntime) }
482
+ formals.evaluate(aRuntime)
418
483
  end
419
484
 
420
485
  def call(aRuntime, aProcedureCall)
@@ -429,6 +494,14 @@ module Skeem
429
494
  result
430
495
  end
431
496
 
497
+ def arity
498
+ formals.arity
499
+ end
500
+
501
+ def required_arity
502
+ formals.required_arity
503
+ end
504
+
432
505
  def inspect
433
506
  result = inspect_prefix + '@formals ' + formals.inspect + ', '
434
507
  result << '@definitions ' + definitions.inspect + ', '
@@ -439,18 +512,29 @@ module Skeem
439
512
  private
440
513
 
441
514
  def bind_locals(aRuntime, aProcedureCall)
442
- arguments = aProcedureCall.operands.members
443
- formals.each_with_index do |arg_name, index|
444
- arg = arguments[index]
445
- if arg.nil?
446
- raise StandardError, "Unbound variable: '#{arg_name.value}'"
515
+ actuals = aProcedureCall.operands.members
516
+ count_actuals = actuals.size
517
+
518
+ if (count_actuals < required_arity) ||
519
+ ((count_actuals > required_arity) && !formals.variadic?)
520
+ raise StandardError, msg_arity_mismatch(aProcedureCall)
521
+ end
522
+ return if count_actuals.zero?
523
+ bind_required_locals(aRuntime, aProcedureCall)
524
+
525
+ if formals.variadic?
526
+ variadic_part_raw = actuals.drop(required_arity)
527
+ variadic_part = variadic_part_raw.map do |actual|
528
+ if actual.kind_of?(ProcedureCall)
529
+ actual.evaluate(aRuntime)
530
+ else
531
+ actual
532
+ end
447
533
  end
448
-
449
- # IMPORTANT: execute procedure call in argument list now
450
- arg = arg.evaluate(aRuntime) if arg.kind_of?(ProcedureCall)
451
- a_def = SkmDefinition.new(position, arg_name, arg)
452
- a_def.evaluate(aRuntime)
453
- # $stderr.puts "LOCAL #{a_def.inspect}"
534
+ variadic_arg_name = formals.formals.last
535
+ args_coll = SkmList.new(variadic_part)
536
+ a_def = SkmDefinition.new(position, variadic_arg_name, args_coll)
537
+ a_def.evaluate(aRuntime)
454
538
  end
455
539
  end
456
540
 
@@ -466,6 +550,33 @@ module Skeem
466
550
 
467
551
  result
468
552
  end
553
+
554
+ def bind_required_locals(aRuntime, aProcedureCall)
555
+ max_index = required_arity - 1
556
+ actuals = aProcedureCall.operands.members
557
+
558
+ formals.formals.each_with_index do |arg_name, index|
559
+ arg = actuals[index]
560
+ if arg.nil?
561
+ raise StandardError, "Unbound variable: '#{arg_name.value}'"
562
+ end
563
+
564
+ # IMPORTANT: execute procedure call in argument list now
565
+ arg = arg.evaluate(aRuntime) if arg.kind_of?(ProcedureCall)
566
+ a_def = SkmDefinition.new(position, arg_name, arg)
567
+ a_def.evaluate(aRuntime)
568
+ # $stderr.puts "LOCAL #{a_def.inspect}"
569
+ break if index >= max_index
570
+ end
571
+ end
572
+
573
+ def msg_arity_mismatch(aProcedureCall)
574
+ # *** ERROR: wrong number of arguments for #<closure morph> (required 2, got 1)
575
+ msg1 = "Wrong number of arguments for procedure #{operator} "
576
+ count_actuals = aProcedureCall.operands.members.size
577
+ msg2 = "(required #{required_arity}, got #{count_actuals})"
578
+ msg1 + msg2
579
+ end
469
580
  end # class
470
581
  end # module
471
582
  # End of file
@@ -18,6 +18,11 @@
18
18
  #t
19
19
  #f)))
20
20
 
21
+ ; For backwards compatibility
22
+ (define modulo
23
+ (lambda (x y)
24
+ (floor-remainder x y)))
25
+
21
26
  (define odd?
22
27
  (lambda (n)
23
28
  (if (= (modulo n 2) 1)
@@ -38,4 +43,7 @@
38
43
 
39
44
  (define square
40
45
  (lambda (z)
41
- (* z z)))
46
+ (* z z)))
47
+
48
+ (define list
49
+ (lambda args args))
@@ -22,7 +22,8 @@ module Skeem
22
22
  "'" => 'APOSTROPHE',
23
23
  '`' => 'BACKQUOTE',
24
24
  '(' => 'LPAREN',
25
- ')' => 'RPAREN'
25
+ ')' => 'RPAREN',
26
+ '.' => 'PERIOD'
26
27
  }.freeze
27
28
 
28
29
  # Here are all the SRL keywords (in uppercase)
@@ -73,7 +74,7 @@ module Skeem
73
74
  if "()'`".include? curr_ch
74
75
  # Delimiters, separators => single character token
75
76
  token = build_token(@@lexeme2name[curr_ch], scanner.getch)
76
- elsif (lexeme = scanner.scan(/#(?:\.)(?=\s|[|()";]|$)/)) # Single char occurring alone
77
+ elsif (lexeme = scanner.scan(/(?:\.)(?=\s|[|()";]|$)/)) # Single char occurring alone
77
78
  token = build_token('PERIOD', lexeme)
78
79
  elsif (lexeme = scanner.scan(/#(?:t|f|true|false)(?=\s|[|()";]|$)/))
79
80
  token = build_token('BOOLEAN', lexeme)
@@ -193,13 +194,13 @@ module Skeem
193
194
  found = scanner.skip(/(?:\r\n)|\r|\n/)
194
195
  if found
195
196
  ws_found = true
196
- @lineno += 1
197
- @line_start = scanner.pos
197
+ next_line
198
198
  end
199
199
  next_ch = scanner.peek(1)
200
200
  if next_ch == ';'
201
201
  cmt_found = true
202
202
  scanner.skip(/;[^\r\n]*(?:(?:\r\n)|\r|\n)?/)
203
+ next_line
203
204
  end
204
205
  break unless ws_found or cmt_found
205
206
  end
@@ -207,6 +208,11 @@ module Skeem
207
208
  curr_pos = scanner.pos
208
209
  return if curr_pos == pre_pos
209
210
  end
211
+
212
+ def next_line
213
+ @lineno += 1
214
+ @line_start = scanner.pos
215
+ end
210
216
  end # class
211
217
  end # module
212
218
  # End of file