skeem 0.0.17 → 0.0.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/lib/skeem/grammar.rb +7 -7
- data/lib/skeem/interpreter.rb +10 -6
- data/lib/skeem/primitive/primitive_builder.rb +205 -131
- data/lib/skeem/primitive/primitive_procedure.rb +111 -0
- data/lib/skeem/s_expr_builder.rb +31 -11
- data/lib/skeem/s_expr_nodes.rb +134 -23
- data/lib/skeem/standard/base.skm +9 -1
- data/lib/skeem/tokenizer.rb +10 -4
- data/lib/skeem/version.rb +1 -1
- data/spec/skeem/interpreter_spec.rb +81 -255
- data/spec/skeem/primitive/primitive_builder_spec.rb +251 -10
- data/spec/skeem/primitive/primitive_procedure_spec.rb +162 -0
- data/spec/skeem/s_expr_nodes_spec.rb +3 -0
- data/spec/skeem/tokenizer_spec.rb +1 -0
- metadata +5 -3
- data/lib/skeem/primitive_procedure.rb +0 -19
@@ -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
|
data/lib/skeem/s_expr_builder.rb
CHANGED
@@ -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('
|
105
|
-
def
|
106
|
-
[theChildren
|
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)
|
data/lib/skeem/s_expr_nodes.rb
CHANGED
@@ -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
|
-
|
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.
|
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
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
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
|
-
|
450
|
-
|
451
|
-
a_def
|
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
|
data/lib/skeem/standard/base.skm
CHANGED
@@ -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))
|
data/lib/skeem/tokenizer.rb
CHANGED
@@ -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(
|
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
|
-
|
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
|