skeem 0.1.03 → 0.2.00
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +1 -1
- data/lib/skeem/grammar.rb +4 -2
- data/lib/skeem/interpreter.rb +24 -9
- data/lib/skeem/primitive/primitive_builder.rb +18 -18
- data/lib/skeem/primitive/primitive_procedure.rb +32 -6
- data/lib/skeem/runtime.rb +34 -32
- data/lib/skeem/s_expr_builder.rb +9 -3
- data/lib/skeem/s_expr_nodes.rb +203 -188
- data/lib/skeem/skm_binding.rb +103 -0
- data/lib/skeem/skm_element.rb +20 -10
- data/lib/skeem/skm_frame.rb +137 -0
- data/lib/skeem/skm_pair.rb +2 -2
- data/lib/skeem/skm_procedure_exec.rb +31 -0
- data/lib/skeem/standard/base.skm +15 -1
- data/lib/skeem/tokenizer.rb +2 -1
- data/lib/skeem/version.rb +1 -1
- data/spec/skeem/interpreter_spec.rb +30 -16
- data/spec/skeem/lambda_spec.rb +114 -0
- data/spec/skeem/primitive/primitive_builder_spec.rb +16 -12
- data/spec/skeem/primitive/primitive_procedure_spec.rb +14 -21
- data/spec/skeem/runtime_spec.rb +14 -12
- data/spec/skeem/s_expr_nodes_spec.rb +5 -81
- data/spec/skeem/skm_frame_spec.rb +119 -0
- data/spec/skeem/skm_procedure_exec_spec.rb +63 -0
- data/spec/skeem/skm_unary_expression_spec.rb +2 -3
- metadata +11 -2
@@ -0,0 +1,103 @@
|
|
1
|
+
require_relative 'skm_element'
|
2
|
+
|
3
|
+
module Skeem
|
4
|
+
|
5
|
+
# An identifier that is not a syntactic keyword can be used as a variable.
|
6
|
+
# A variable may give a name to value that is bound (i.e. associated)
|
7
|
+
# to that variable.
|
8
|
+
class SkmBinding < SkmElement
|
9
|
+
# @return [SkmIdentifier] The identifier that is bound the value.
|
10
|
+
attr_reader(:variable)
|
11
|
+
|
12
|
+
# @return [SkmElement] The Skeem object that is associated with the variable.
|
13
|
+
attr_reader(:value)
|
14
|
+
|
15
|
+
# Constructor
|
16
|
+
# @param anIdentifier [SkmIdentifier] The variable name
|
17
|
+
# @param aValue [SkmElement] The value to bind to the variable.
|
18
|
+
def initialize(anIdentifier, aValue)
|
19
|
+
@variable = anIdentifier
|
20
|
+
@value = aValue
|
21
|
+
end
|
22
|
+
|
23
|
+
def evaluate(aRuntime)
|
24
|
+
name = variable.evaluate(aRuntime)
|
25
|
+
|
26
|
+
if value.kind_of?(SkmVariableReference)
|
27
|
+
other_name = value.variable.evaluate(aRuntime)
|
28
|
+
if name.value != other_name.value
|
29
|
+
entry = aRuntime.fetch(other_name)
|
30
|
+
result = value.evaluate(aRuntime)
|
31
|
+
if entry.callable?
|
32
|
+
@value = entry
|
33
|
+
end
|
34
|
+
else
|
35
|
+
# same name in definiens
|
36
|
+
raise StandardError
|
37
|
+
end
|
38
|
+
else
|
39
|
+
result = value.evaluate(aRuntime)
|
40
|
+
end
|
41
|
+
|
42
|
+
=begin
|
43
|
+
aRuntime.add_binding(var_key, self)
|
44
|
+
case expression
|
45
|
+
when SkmLambda
|
46
|
+
result = expression.evaluate(aRuntime)
|
47
|
+
|
48
|
+
when SkmVariableReference
|
49
|
+
other_key = expression.variable.evaluate(aRuntime)
|
50
|
+
if var_key.value != other_key.value
|
51
|
+
entry = aRuntime.fetch(other_key)
|
52
|
+
result = expression.evaluate(aRuntime)
|
53
|
+
if entry.kind_of?(Primitive::PrimitiveProcedure)
|
54
|
+
@expression = entry
|
55
|
+
elsif entry.kind_of?(SkmDefinition)
|
56
|
+
if entry.expression.kind_of?(SkmLambda)
|
57
|
+
@expression = entry.expression
|
58
|
+
end
|
59
|
+
end
|
60
|
+
else
|
61
|
+
# INFINITE LOOP DANGER: definition of 'x' has a reference to 'x'!
|
62
|
+
# Way out: the lookup for the reference should start from outer
|
63
|
+
# environment.
|
64
|
+
env = aRuntime.pop
|
65
|
+
@expression = expression.evaluate(aRuntime)
|
66
|
+
aRuntime.push(env)
|
67
|
+
result = expression
|
68
|
+
end
|
69
|
+
else
|
70
|
+
result = self
|
71
|
+
end
|
72
|
+
=end
|
73
|
+
binding_action(aRuntime, name, result)
|
74
|
+
result
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
def binding_action(aRuntime, anIdentifier, anExpression)
|
80
|
+
aRuntime.add_binding(anIdentifier, anExpression)
|
81
|
+
end
|
82
|
+
|
83
|
+
def inspect_specific
|
84
|
+
result = variable.inspect
|
85
|
+
if value
|
86
|
+
result << ', ' << value.inspect
|
87
|
+
end
|
88
|
+
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
end # class
|
93
|
+
|
94
|
+
|
95
|
+
class SkmUpdateBinding < SkmBinding
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def binding_action(aRuntime, anIdentifier, anExpression)
|
100
|
+
aRuntime.update_binding(anIdentifier, anExpression)
|
101
|
+
end
|
102
|
+
end # class
|
103
|
+
end # module
|
data/lib/skeem/skm_element.rb
CHANGED
@@ -4,6 +4,10 @@ module Skeem
|
|
4
4
|
def initialize(aPosition)
|
5
5
|
self.position = aPosition
|
6
6
|
end
|
7
|
+
|
8
|
+
def callable?
|
9
|
+
false
|
10
|
+
end
|
7
11
|
|
8
12
|
def number?
|
9
13
|
false
|
@@ -36,7 +40,7 @@ module Skeem
|
|
36
40
|
def null?
|
37
41
|
false
|
38
42
|
end
|
39
|
-
|
43
|
+
|
40
44
|
def pair?
|
41
45
|
false
|
42
46
|
end
|
@@ -44,16 +48,16 @@ module Skeem
|
|
44
48
|
def vector?
|
45
49
|
false
|
46
50
|
end
|
47
|
-
|
51
|
+
|
48
52
|
def eqv?(other)
|
49
53
|
equal?(other)
|
50
54
|
end
|
51
|
-
|
55
|
+
|
52
56
|
def skm_equal?(_other)
|
53
57
|
msg = "Missing implementation of method #{self.class.name}##{__method__}"
|
54
58
|
raise NotImplementedError, msg
|
55
59
|
end
|
56
|
-
|
60
|
+
|
57
61
|
# @return [TrueClass, FalseClass] true if quoted element is identical to itself
|
58
62
|
def verbatim?
|
59
63
|
false
|
@@ -62,10 +66,10 @@ module Skeem
|
|
62
66
|
def evaluate(_runtime)
|
63
67
|
raise NotImplementedError, "Missing implementation of #{self.class.name}"
|
64
68
|
end
|
65
|
-
|
69
|
+
|
66
70
|
def quasiquote(_runtime)
|
67
71
|
raise NotImplementedError, "Missing implementation of #{self.class.name}"
|
68
|
-
end
|
72
|
+
end
|
69
73
|
|
70
74
|
# Abstract method.
|
71
75
|
# Part of the 'visitee' role in Visitor design pattern.
|
@@ -77,14 +81,20 @@ module Skeem
|
|
77
81
|
def done!
|
78
82
|
# Do nothing
|
79
83
|
end
|
80
|
-
|
84
|
+
|
81
85
|
def quoted!
|
82
86
|
# Do nothing
|
83
87
|
end
|
84
|
-
|
88
|
+
|
85
89
|
def unquoted!
|
86
90
|
# Do nothing
|
87
|
-
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Notification that this procedure is bound to a variable
|
94
|
+
# @param [Skemm::SkmFrame]
|
95
|
+
def bound!(_frame)
|
96
|
+
# Do nothing
|
97
|
+
end
|
88
98
|
|
89
99
|
def inspect
|
90
100
|
result = inspect_prefix
|
@@ -105,7 +115,7 @@ module Skeem
|
|
105
115
|
end
|
106
116
|
|
107
117
|
def inspect_specific
|
108
|
-
raise NotImplementedError
|
118
|
+
raise NotImplementedError, "Missing #{self.class.to_s + '#' + 'inspect_specific'}"
|
109
119
|
end
|
110
120
|
end # struct
|
111
121
|
end # module
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require_relative 'skm_simple_datum'
|
2
|
+
|
3
|
+
module Skeem
|
4
|
+
# A frame is a set of bindings of the form { String => SkmElement }.
|
5
|
+
# It associate to each identifier (as String) a Skeem object.
|
6
|
+
class SkmFrame
|
7
|
+
# @return [SkmFrame, nil] Link to parent frame (if any).
|
8
|
+
attr_reader :parent
|
9
|
+
|
10
|
+
# @return [Hash{String => SkmElement}] Map of variable names => values.
|
11
|
+
attr_reader(:bindings)
|
12
|
+
|
13
|
+
# Constructor
|
14
|
+
# @param parentFrame[SkmFrame] Parent frame of this one.
|
15
|
+
def initialize(parentFrame = nil)
|
16
|
+
@bindings = {}
|
17
|
+
@parent = parentFrame
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add a binding to this frame.
|
21
|
+
# There is no check that the variable name is already in use.
|
22
|
+
# @param anIdentifier [String, SkmIdentifier] The variable name
|
23
|
+
# @param anExpression [SkmElement] The Skeem expression to bind
|
24
|
+
def add_binding(anIdentifier, anExpression)
|
25
|
+
bind(valid_identifier(anIdentifier), anExpression)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Update the value of an existing variable.
|
29
|
+
# @param anIdentifier [String, SkmIdentifier] The variable name.
|
30
|
+
# @param anExpression [SkmElement] The Skeem expression to bind
|
31
|
+
def update_binding(anIdentifier, anExpression)
|
32
|
+
variable = valid_identifier(anIdentifier)
|
33
|
+
if bindings.include?(variable)
|
34
|
+
bind(variable, anExpression)
|
35
|
+
elsif parent
|
36
|
+
parent.update_binding(variable, anExpression)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Retrieve the value of an existing variable.
|
41
|
+
# @param anIdentifier [String, SkmIdentifier] The variable name.
|
42
|
+
# @return [SkmElement]
|
43
|
+
def fetch(anIdentifier)
|
44
|
+
variable = valid_identifier(anIdentifier)
|
45
|
+
found = bindings[variable]
|
46
|
+
if found.nil? && parent
|
47
|
+
found = parent.fetch(variable)
|
48
|
+
end
|
49
|
+
|
50
|
+
found
|
51
|
+
end
|
52
|
+
|
53
|
+
# Tell the value of an existing variable.
|
54
|
+
# @param anIdentifier [String, SkmIdentifier] The variable name.
|
55
|
+
# @return [Boolean]
|
56
|
+
def include?(anIdentifier)
|
57
|
+
variable = valid_identifier(anIdentifier)
|
58
|
+
my_result = bindings.include?(variable)
|
59
|
+
if my_result == false && parent
|
60
|
+
my_result = parent.include?(variable)
|
61
|
+
end
|
62
|
+
|
63
|
+
my_result
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Boolean]
|
67
|
+
def empty?
|
68
|
+
my_result = bindings.empty?
|
69
|
+
if my_result && parent
|
70
|
+
my_result = parent.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
my_result
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [Integer]
|
77
|
+
def size
|
78
|
+
my_result = bindings.size
|
79
|
+
my_result += parent.size if parent
|
80
|
+
|
81
|
+
my_result
|
82
|
+
end
|
83
|
+
|
84
|
+
# The number of parents this frame has.
|
85
|
+
# @return [Integer] The nesting levels
|
86
|
+
def depth
|
87
|
+
count = 0
|
88
|
+
|
89
|
+
curr_frame = self
|
90
|
+
while curr_frame.parent
|
91
|
+
count += 1
|
92
|
+
curr_frame = curr_frame.parent
|
93
|
+
end
|
94
|
+
|
95
|
+
count
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [String]
|
99
|
+
def inspect
|
100
|
+
result = ''
|
101
|
+
if parent
|
102
|
+
result << parent.inspect
|
103
|
+
else
|
104
|
+
return "\n"
|
105
|
+
end
|
106
|
+
result << "\n----\n"
|
107
|
+
bindings.each_pair do |key, expr|
|
108
|
+
result << "#{key.inspect} => #{expr.inspect}\n"
|
109
|
+
end
|
110
|
+
|
111
|
+
result
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def valid_identifier(anIdentifier)
|
117
|
+
case anIdentifier
|
118
|
+
when String
|
119
|
+
anIdentifier
|
120
|
+
when SkmIdentifier
|
121
|
+
anIdentifier.value
|
122
|
+
else
|
123
|
+
klass = anIdentifier.class.to_s
|
124
|
+
err_msg = "Invalid identifier: #{anIdentifier} has type #{klass}."
|
125
|
+
raise StandardError, err_msg
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def bind(anIdentifier, anExpression)
|
130
|
+
@bindings[anIdentifier] = anExpression
|
131
|
+
|
132
|
+
# Notify the value that it is bound to a variable from this frame.
|
133
|
+
anExpression.bound!(self)
|
134
|
+
end
|
135
|
+
|
136
|
+
end # class
|
137
|
+
end # module
|
data/lib/skeem/skm_pair.rb
CHANGED
@@ -133,8 +133,8 @@ module Skeem
|
|
133
133
|
begin
|
134
134
|
result = clone_evaluate(aRuntime)
|
135
135
|
rescue NoMethodError => exc
|
136
|
-
$stderr.puts self.inspect
|
137
|
-
$stderr.puts self.to_a.inspect
|
136
|
+
$stderr.puts 'SkmPair#evaluate: ' + self.inspect
|
137
|
+
$stderr.puts 'SkmPair as Array: ' + self.to_a.inspect
|
138
138
|
raise exc
|
139
139
|
end
|
140
140
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'runtime'
|
2
|
+
|
3
|
+
module Skeem
|
4
|
+
class SkmProcedureExec
|
5
|
+
attr_reader :frame
|
6
|
+
attr_reader :definition
|
7
|
+
|
8
|
+
def initialize(aLambda)
|
9
|
+
@definition = aLambda
|
10
|
+
@frame = SkmFrame.new(definition.environment)
|
11
|
+
# $stderr.puts "New SkmProcedureExec"
|
12
|
+
# $stderr.puts " frame = #{frame.object_id.to_s(16)}"
|
13
|
+
# $stderr.puts " Lambda = #{aLambda.object_id.to_s(16)}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param theActuals [Array<SkmElement>]
|
17
|
+
def run!(aRuntime, theActuals)
|
18
|
+
runtime = aRuntime
|
19
|
+
runtime.push(frame)
|
20
|
+
definition.bind_locals(runtime, theActuals)
|
21
|
+
# $stderr.puts "Locals"
|
22
|
+
# $stderr.puts frame.bindings.keys.join(', ')
|
23
|
+
definition.evaluate_defs(runtime)
|
24
|
+
result = definition.evaluate_sequence(runtime)
|
25
|
+
runtime.pop
|
26
|
+
# $stderr.puts "Lambda result: #{result.object_id.to_s(16)}" if result.kind_of?(SkmLambda)
|
27
|
+
|
28
|
+
result
|
29
|
+
end
|
30
|
+
end # class
|
31
|
+
end # module
|
data/lib/skeem/standard/base.skm
CHANGED
@@ -98,4 +98,18 @@
|
|
98
98
|
; (car ls)
|
99
99
|
; (list-ref (cdr ls) (- n 1)))))
|
100
100
|
|
101
|
-
(define symbol=? string=?)
|
101
|
+
(define symbol=? string=?)
|
102
|
+
|
103
|
+
;; Test the equivalence (with eqv? predicate) between an expected value and
|
104
|
+
;; an expression
|
105
|
+
;; (test-eqv expected test-expr)
|
106
|
+
(define test-eqv
|
107
|
+
(lambda (x y)
|
108
|
+
(test-assert (equal? x y))))
|
109
|
+
|
110
|
+
;; Test the equality (with equal? predicate) between an expected value and
|
111
|
+
;; an expression
|
112
|
+
;; (test-equal expected test-expr)
|
113
|
+
(define test-equal
|
114
|
+
(lambda (x y)
|
115
|
+
(test-assert (equal? x y))))
|
data/lib/skeem/tokenizer.rb
CHANGED
@@ -29,7 +29,7 @@ module Skeem
|
|
29
29
|
'#(' => 'VECTOR_BEGIN'
|
30
30
|
}.freeze
|
31
31
|
|
32
|
-
# Here are all the
|
32
|
+
# Here are all the Scheme keywords (in uppercase)
|
33
33
|
@@keywords = %w[
|
34
34
|
BEGIN
|
35
35
|
DEFINE
|
@@ -37,6 +37,7 @@ module Skeem
|
|
37
37
|
LAMBDA
|
38
38
|
QUASIQUOTE
|
39
39
|
QUOTE
|
40
|
+
SET!
|
40
41
|
UNQUOTE
|
41
42
|
UNQUOTE-SPLICING
|
42
43
|
].map { |x| [x, x] } .to_h
|
data/lib/skeem/version.rb
CHANGED
@@ -8,8 +8,12 @@ module Skeem
|
|
8
8
|
include DatumDSL
|
9
9
|
|
10
10
|
context 'Initialization:' do
|
11
|
-
it '
|
12
|
-
expect { Interpreter.new
|
11
|
+
it 'could be initialized without an argument' do
|
12
|
+
expect { Interpreter.new }.not_to raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'could be initialized with a block argument' do
|
16
|
+
expect { Interpreter.new { |interp| } }.not_to raise_error
|
13
17
|
end
|
14
18
|
|
15
19
|
it 'should have a parser' do
|
@@ -26,8 +30,9 @@ module Skeem
|
|
26
30
|
|
27
31
|
it 'should implement base bindings' do
|
28
32
|
expect(subject.fetch('number?')).to be_kind_of(Primitive::PrimitiveProcedure)
|
29
|
-
expect(subject.fetch('abs')).to be_kind_of(
|
30
|
-
expect(subject.fetch('abs').
|
33
|
+
expect(subject.fetch('abs')).to be_kind_of(SkmLambda)
|
34
|
+
expect(subject.fetch('abs').formals.arity).to eq(1)
|
35
|
+
expect(subject.fetch('abs').formals.formals[0]).to eq('x')
|
31
36
|
end
|
32
37
|
end # context
|
33
38
|
|
@@ -107,7 +112,7 @@ module Skeem
|
|
107
112
|
context 'Built-in primitives' do
|
108
113
|
it 'should implement variable definition' do
|
109
114
|
result = subject.run('(define x 28)')
|
110
|
-
expect(
|
115
|
+
expect(subject.fetch('x')).to eq(28)
|
111
116
|
end
|
112
117
|
|
113
118
|
it 'should implement variable reference' do
|
@@ -125,7 +130,7 @@ SKEEM
|
|
125
130
|
it 'should implement the simple conditional form' do
|
126
131
|
checks = [
|
127
132
|
['(if (> 3 2) "yes")', 'yes'],
|
128
|
-
['(if (> 2 3) "yes")',
|
133
|
+
['(if (> 2 3) "yes")', SkmUndefined.instance]
|
129
134
|
]
|
130
135
|
checks.each do |(skeem_expr, expectation)|
|
131
136
|
result = subject.run(skeem_expr)
|
@@ -213,7 +218,7 @@ SKEEM
|
|
213
218
|
(if (< x 0) (- x) x)))
|
214
219
|
SKEEM
|
215
220
|
subject.run(source)
|
216
|
-
procedure = subject.fetch('abs')
|
221
|
+
procedure = subject.fetch('abs')
|
217
222
|
expect(procedure.arity).to eq(1)
|
218
223
|
result = subject.run('(abs -3)')
|
219
224
|
expect(result).to eq(3)
|
@@ -231,7 +236,7 @@ SKEEM
|
|
231
236
|
(if (< x y) x y)))
|
232
237
|
SKEEM
|
233
238
|
subject.run(source)
|
234
|
-
procedure = subject.fetch('min')
|
239
|
+
procedure = subject.fetch('min')
|
235
240
|
expect(procedure.arity).to eq(2)
|
236
241
|
result = subject.run('(min 1 2)')
|
237
242
|
expect(result).to eq(1)
|
@@ -631,7 +636,7 @@ SKEEM
|
|
631
636
|
expect(result.to_a).to eq(expectation)
|
632
637
|
end
|
633
638
|
end
|
634
|
-
|
639
|
+
|
635
640
|
it 'should implement the cddr procedure' do
|
636
641
|
checks = [
|
637
642
|
["(cddr '(2 1))", []]
|
@@ -640,7 +645,7 @@ SKEEM
|
|
640
645
|
result = subject.run(skeem_expr)
|
641
646
|
expect(result.to_a).to eq(expectation)
|
642
647
|
end
|
643
|
-
end
|
648
|
+
end
|
644
649
|
|
645
650
|
it 'should implement the symbol=? procedure' do
|
646
651
|
checks = [
|
@@ -656,19 +661,27 @@ SKEEM
|
|
656
661
|
end # context
|
657
662
|
|
658
663
|
context 'More advanced tests' do
|
659
|
-
it 'should implement second-order functions' do
|
664
|
+
it 'should implement lambda that calls second-order functions' do
|
660
665
|
source = <<-SKEEM
|
666
|
+
(define twice
|
667
|
+
(lambda (x)
|
668
|
+
(* 2 x)))
|
661
669
|
(define compose
|
662
670
|
(lambda (f g)
|
663
671
|
(lambda (x)
|
664
672
|
(f (g x)))))
|
665
|
-
(
|
673
|
+
(define repeat
|
674
|
+
(lambda (f)
|
675
|
+
(compose f f)))
|
676
|
+
((repeat twice) 5)
|
666
677
|
SKEEM
|
667
678
|
result = subject.run(source)
|
668
|
-
expect(result.last
|
679
|
+
expect(result.last).to eq(20)
|
669
680
|
end
|
670
681
|
|
671
|
-
|
682
|
+
#=begin
|
683
|
+
it 'under construction' do
|
684
|
+
# Fails
|
672
685
|
source = <<-SKEEM
|
673
686
|
(define twice
|
674
687
|
(lambda (x)
|
@@ -680,11 +693,12 @@ SKEEM
|
|
680
693
|
(define repeat
|
681
694
|
(lambda (f)
|
682
695
|
(compose f f)))
|
683
|
-
((repeat twice) 5)
|
696
|
+
((repeat (repeat twice)) 5)
|
684
697
|
SKEEM
|
685
698
|
result = subject.run(source)
|
686
|
-
expect(result.last).to eq(
|
699
|
+
expect(result.last).to eq(80)
|
687
700
|
end
|
701
|
+
#=end
|
688
702
|
end # context
|
689
703
|
end # describe
|
690
704
|
end # module
|