skeem 0.0.17 → 0.0.18

Sign up to get free protection for your applications and to get access to all the features.
@@ -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