skeem 0.1.03 → 0.2.00
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 +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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f8a75ad0a0ead374dc7ec59e94abc9edd04826901be7f2e8230c9047c256845
|
4
|
+
data.tar.gz: 854f4e2e64dff8224046420ee7ccd035b23cf409c016ecec667206c02a5aae87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea925e74fbbab68290d6b6cb13e7af3caa735e9f5709e5d9f50423410695d5db0a9c50d077e594e2683e16c7b4f6b85c02b1d72629d97dc6e9348f7a1e32533c
|
7
|
+
data.tar.gz: 4930db824c351c76d56f006491f8838e88b64e2e2200fb13412c848c0bc90c6f9effa839983ac3e5bd926e0c0ab162bfb87e3a8217f8d2f14410ce35a4ff2d46
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## [0.1.04] - 2019-01-19
|
2
|
+
### Added
|
3
|
+
- Method `SkmDefinition#redefine`: a given variable can point to another expression.
|
4
|
+
|
5
|
+
### Fixed
|
6
|
+
- Procedure `set!` caused infinite loops when redefinition involved a procedure call.
|
7
|
+
|
1
8
|
## [0.1.03] - 2019-01-15
|
2
9
|
### Added
|
3
10
|
- File `base.skm` implementation of list procedures: `caar`, `cadr`, `cdar`, `cddr`
|
data/README.md
CHANGED
@@ -222,7 +222,7 @@ This project is intended to be a safe, welcoming space for collaboration, and co
|
|
222
222
|
|
223
223
|
Copyright
|
224
224
|
---------
|
225
|
-
Copyright (c) 2018, Dimitri Geshef.
|
225
|
+
Copyright (c) 2018-2019, Dimitri Geshef.
|
226
226
|
__Skeem__ is released under the MIT License see [LICENSE.txt](https://github.com/famished-tiger/Skeem/blob/master/LICENSE.txt) for details.
|
227
227
|
|
228
228
|
## Code of Conduct
|
data/lib/skeem/grammar.rb
CHANGED
@@ -20,8 +20,8 @@ module Skeem
|
|
20
20
|
|
21
21
|
# Keywords...
|
22
22
|
add_terminals('DEFINE', 'IF', 'LAMBDA')
|
23
|
-
add_terminals('QUOTE', 'QUASIQUOTE', '
|
24
|
-
add_terminals('UNQUOTE-SPLICING')
|
23
|
+
add_terminals('QUOTE', 'QUASIQUOTE', 'SET!')
|
24
|
+
add_terminals('UNQUOTE', 'UNQUOTE-SPLICING')
|
25
25
|
|
26
26
|
rule('program' => 'cmd_or_def_plus').as 'main'
|
27
27
|
rule('cmd_or_def_plus' => 'cmd_or_def_plus cmd_or_def').as 'multiple_cmd_def'
|
@@ -36,6 +36,7 @@ module Skeem
|
|
36
36
|
rule 'expression' => 'procedure_call'
|
37
37
|
rule 'expression' => 'lambda_expression'
|
38
38
|
rule 'expression' => 'conditional'
|
39
|
+
rule 'expression' => 'assignment'
|
39
40
|
rule 'expression' => 'derived_expression'
|
40
41
|
rule 'literal' => 'quotation'
|
41
42
|
rule 'literal' => 'self-evaluating'
|
@@ -90,6 +91,7 @@ module Skeem
|
|
90
91
|
rule 'alternate' => []
|
91
92
|
rule 'number' => 'INTEGER'
|
92
93
|
rule 'number' => 'REAL'
|
94
|
+
rule('assignment' => 'LPAREN SET! IDENTIFIER expression RPAREN').as 'assignment'
|
93
95
|
rule 'derived_expression' => 'quasiquotation'
|
94
96
|
rule('quasiquotation' => 'LPAREN QUASIQUOTE qq_template RPAREN').as 'quasiquotation'
|
95
97
|
rule('quasiquotation' => 'GRAVE_ACCENT qq_template').as 'quasiquotation_short'
|
data/lib/skeem/interpreter.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require_relative 'parser'
|
2
|
-
require_relative '
|
2
|
+
require_relative 'skm_frame'
|
3
3
|
require_relative 'runtime'
|
4
4
|
require_relative './primitive/primitive_builder'
|
5
5
|
|
@@ -9,31 +9,46 @@ module Skeem
|
|
9
9
|
attr_reader(:parser)
|
10
10
|
attr_reader(:runtime)
|
11
11
|
|
12
|
-
def initialize()
|
13
|
-
@runtime = Runtime.new(
|
12
|
+
def initialize(&aBlock)
|
13
|
+
@runtime = Runtime.new(SkmFrame.new)
|
14
|
+
@parser = Parser.new
|
15
|
+
|
16
|
+
if block_given?
|
17
|
+
yield self
|
18
|
+
else
|
19
|
+
add_default_procedures
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_default_procedures()
|
14
24
|
add_primitives(runtime)
|
15
25
|
add_standard(runtime)
|
16
26
|
end
|
17
27
|
|
18
|
-
def
|
28
|
+
def parse(source, mode = nil)
|
19
29
|
@parser ||= Parser.new
|
20
30
|
@ptree = parser.parse(source)
|
21
31
|
# $stderr.puts @ptree.root.inspect if mode.nil?
|
22
|
-
# require 'debug'
|
23
|
-
|
32
|
+
# require 'debug' unless mode.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def run(source, mode = nil)
|
36
|
+
parse(source, mode)
|
37
|
+
# require 'debug' unless mode.nil?
|
38
|
+
@ptree.root.evaluate(runtime)
|
24
39
|
end
|
25
40
|
|
26
41
|
def fetch(anIdentifier)
|
27
42
|
runtime.environment.fetch(anIdentifier)
|
28
43
|
end
|
29
44
|
|
30
|
-
|
31
|
-
|
32
|
-
def add_standard(aRuntime)
|
45
|
+
def add_standard(_runtime)
|
33
46
|
std_pathname = File.dirname(__FILE__) + '/standard/base.skm'
|
34
47
|
load_lib(std_pathname)
|
35
48
|
end
|
36
49
|
|
50
|
+
private
|
51
|
+
|
37
52
|
def load_lib(aPathname)
|
38
53
|
lib_source = nil
|
39
54
|
File.open(aPathname, 'r') do |lib|
|
@@ -48,7 +48,6 @@ module Skeem
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def add_binding(aRuntime)
|
51
|
-
create_set!(aRuntime)
|
52
51
|
end
|
53
52
|
|
54
53
|
def add_arithmetic(aRuntime)
|
@@ -123,21 +122,9 @@ module Skeem
|
|
123
122
|
end
|
124
123
|
|
125
124
|
def add_special_procedures(aRuntime)
|
126
|
-
|
125
|
+
create_test_assert(aRuntime)
|
127
126
|
create_debug(aRuntime)
|
128
|
-
|
129
|
-
|
130
|
-
def create_set!(aRuntime)
|
131
|
-
primitive = ->(runtime, var_ref, expr) do
|
132
|
-
if runtime.include?(var_ref.child)
|
133
|
-
redefinition = SkmDefinition.new(nil, var_ref.child, expr)
|
134
|
-
redefinition.evaluate(runtime)
|
135
|
-
else
|
136
|
-
raise StandardError, "Unbound variable: '#{var.value}'"
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
define_primitive_proc(aRuntime, 'set!', binary, primitive)
|
127
|
+
create_inspect(aRuntime)
|
141
128
|
end
|
142
129
|
|
143
130
|
def create_plus(aRuntime)
|
@@ -622,7 +609,7 @@ module Skeem
|
|
622
609
|
define_primitive_proc(aRuntime, 'newline', nullary, primitive)
|
623
610
|
end
|
624
611
|
|
625
|
-
def
|
612
|
+
def create_test_assert(aRuntime)
|
626
613
|
primitive = ->(runtime, arg) do
|
627
614
|
arg_evaluated = arg.evaluate(runtime)
|
628
615
|
if arg_evaluated.boolean? && arg_evaluated.value == false
|
@@ -636,9 +623,11 @@ module Skeem
|
|
636
623
|
end
|
637
624
|
end
|
638
625
|
|
639
|
-
define_primitive_proc(aRuntime, 'assert', unary, primitive)
|
626
|
+
define_primitive_proc(aRuntime, 'test-assert', unary, primitive)
|
640
627
|
end
|
641
628
|
|
629
|
+
# DON'T USE IT
|
630
|
+
# Non-standard procedure reserved for internal testing/debugging purposes.
|
642
631
|
def create_debug(aRuntime)
|
643
632
|
primitive = ->(runtime) do
|
644
633
|
require 'debug'
|
@@ -646,6 +635,17 @@ module Skeem
|
|
646
635
|
|
647
636
|
define_primitive_proc(aRuntime, 'debug', nullary, primitive)
|
648
637
|
end
|
638
|
+
|
639
|
+
# DON'T USE IT
|
640
|
+
# Non-standard procedure reserved for internal testing/debugging purposes.
|
641
|
+
def create_inspect(aRuntime)
|
642
|
+
primitive = ->(runtime, arg) do
|
643
|
+
arg_evaluated = arg.evaluate(runtime)
|
644
|
+
$stderr.puts 'INSPECT>' + arg_evaluated.inspect
|
645
|
+
Skeem::SkmUndefined.instance
|
646
|
+
end
|
647
|
+
define_primitive_proc(aRuntime, '_inspect', unary, primitive)
|
648
|
+
end
|
649
649
|
|
650
650
|
def create_object_predicate(aRuntime, predicate_name, msg_name = nil)
|
651
651
|
msg_name = predicate_name if msg_name.nil?
|
@@ -670,7 +670,7 @@ module Skeem
|
|
670
670
|
end
|
671
671
|
|
672
672
|
def define(aRuntime, aKey, anEntry)
|
673
|
-
aRuntime.
|
673
|
+
aRuntime.add_binding(aKey, anEntry)
|
674
674
|
end
|
675
675
|
|
676
676
|
def evaluate_arguments(arglist, aRuntime)
|
@@ -14,18 +14,44 @@ module Skeem
|
|
14
14
|
@arity = arity_validated(anArity)
|
15
15
|
end
|
16
16
|
|
17
|
+
def callable?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
# This method should be invoked when the procedure isn't
|
22
|
+
# explicitly called (with arguments). In this case, the name
|
23
|
+
# of procedure just returns the procedure object itself.
|
24
|
+
# @param _runtime [Runtime]
|
25
|
+
# @return [PrimitiveProcedure]
|
26
|
+
def evaluate(_runtime)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
17
30
|
# Arguments are positional in a primitive procedure.
|
18
|
-
|
19
|
-
|
31
|
+
# @param theActuals [Array<SkmElement>]
|
32
|
+
def call(aRuntime, theActuals)
|
33
|
+
actuals = theActuals
|
34
|
+
# $stderr.puts "--- Start of procedure #{identifier}"
|
35
|
+
# actuals.each { |actual| $stderr.puts ' Actual: ' + actual.inspect }
|
20
36
|
check_actual_count(actuals)
|
21
|
-
|
22
|
-
|
37
|
+
# TODO: check that next line became useless
|
38
|
+
# aProcedureCall.operands_consumed = true
|
39
|
+
result = do_call(aRuntime, actuals)
|
40
|
+
# $stderr.puts " Result: #{result.inspect}"
|
41
|
+
# $stderr.puts "--- End of procedure #{identifier}"
|
42
|
+
result
|
23
43
|
end
|
24
|
-
|
44
|
+
|
25
45
|
def skm_equal?(other)
|
26
46
|
equal?(other)
|
27
47
|
end
|
28
48
|
|
49
|
+
# Notification that this procedure is bound to a variable
|
50
|
+
# @param [Skemm::SkmFrame]
|
51
|
+
def bound!(_frame)
|
52
|
+
# Do nothing
|
53
|
+
end
|
54
|
+
|
29
55
|
private
|
30
56
|
|
31
57
|
def code_validated(aRubyLambda)
|
@@ -94,7 +120,7 @@ module Skeem
|
|
94
120
|
#p count_delta
|
95
121
|
#p arguments.inspect
|
96
122
|
result = code.send(:call, aRuntime, *arguments.flatten)
|
97
|
-
end
|
123
|
+
end
|
98
124
|
else # Fixed arity...
|
99
125
|
result = code.send(:call, aRuntime, *operands)
|
100
126
|
end
|
data/lib/skeem/runtime.rb
CHANGED
@@ -1,30 +1,37 @@
|
|
1
1
|
require_relative 's_expr_nodes'
|
2
|
-
require_relative 'environment'
|
3
2
|
|
4
3
|
module Skeem
|
5
4
|
class Runtime
|
6
|
-
# @return [
|
7
|
-
attr_reader(:
|
5
|
+
# @return [Array<SkmFrame>] The current active frame
|
6
|
+
attr_reader(:env_stack)
|
8
7
|
|
9
8
|
# @return [Array<ProcedureCall>] The call stack
|
10
9
|
attr_reader(:call_stack)
|
11
10
|
|
12
|
-
def initialize(anEnvironment)
|
13
|
-
@
|
14
|
-
|
11
|
+
def initialize(anEnvironment, parent = nil)
|
12
|
+
@env_stack = []
|
13
|
+
push(anEnvironment)
|
14
|
+
@call_stack = parent.nil? ? [] : parent.call_stack
|
15
|
+
end
|
16
|
+
|
17
|
+
def environment
|
18
|
+
env_stack.last
|
15
19
|
end
|
16
20
|
|
17
21
|
def include?(anIdentifier)
|
18
|
-
environment.include?(
|
22
|
+
environment.include?(anIdentifier)
|
19
23
|
end
|
20
24
|
|
21
25
|
def fetch(aKey)
|
22
|
-
|
23
|
-
include?(key_value) ? environment.fetch(key_value) : nil
|
26
|
+
include?(aKey) ? environment.fetch(aKey) : nil
|
24
27
|
end
|
25
28
|
|
26
|
-
def
|
27
|
-
environment.
|
29
|
+
def add_binding(aKey, anEntry)
|
30
|
+
environment.add_binding(aKey, anEntry)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_binding(aKey, anEntry)
|
34
|
+
environment.update_binding(aKey, anEntry)
|
28
35
|
end
|
29
36
|
|
30
37
|
def evaluate(aKey)
|
@@ -33,14 +40,8 @@ module Skeem
|
|
33
40
|
entry = environment.fetch(key_value)
|
34
41
|
result = nil
|
35
42
|
begin
|
36
|
-
|
37
|
-
|
38
|
-
result = entry
|
39
|
-
when SkmDefinition
|
40
|
-
result = entry.expression.evaluate(self)
|
41
|
-
else
|
42
|
-
raise StandardError, entry.inspect
|
43
|
-
end
|
43
|
+
result = entry.evaluate(self)
|
44
|
+
|
44
45
|
rescue NoMethodError => exc
|
45
46
|
# $stderr.puts 'In rescue block'
|
46
47
|
# $stderr.puts key_value.inspect
|
@@ -66,29 +67,30 @@ module Skeem
|
|
66
67
|
end
|
67
68
|
|
68
69
|
def nest()
|
69
|
-
nested =
|
70
|
-
|
70
|
+
nested = SkmFrame.new(environment)
|
71
|
+
push(nested)
|
71
72
|
end
|
72
73
|
|
73
|
-
def unnest
|
74
|
-
raise StandardError, 'Cannot unnest environment' unless environment.
|
74
|
+
def unnest
|
75
|
+
raise StandardError, 'Cannot unnest environment' unless environment.parent
|
75
76
|
environment.bindings.clear
|
76
|
-
|
77
|
+
pop
|
77
78
|
end
|
78
79
|
|
79
|
-
def depth
|
80
|
-
return
|
80
|
+
def depth
|
81
|
+
return env_stack.size
|
81
82
|
end
|
82
83
|
|
83
84
|
def push(anEnvironment)
|
84
|
-
|
85
|
+
env_stack.push(anEnvironment)
|
85
86
|
end
|
86
87
|
|
87
|
-
# Make the
|
88
|
+
# Make the parent frame the current one inside the provided block
|
88
89
|
def pop
|
89
|
-
|
90
|
-
|
91
|
-
|
90
|
+
if env_stack.empty?
|
91
|
+
raise StandardError, 'Skeem environment stack empty!'
|
92
|
+
end
|
93
|
+
env_stack.pop
|
92
94
|
end
|
93
95
|
|
94
96
|
def push_call(aProcCall)
|
@@ -104,7 +106,7 @@ module Skeem
|
|
104
106
|
# $stderr.puts 'CALL STACK ^^^^'
|
105
107
|
end
|
106
108
|
|
107
|
-
def pop_call
|
109
|
+
def pop_call
|
108
110
|
if call_stack.empty?
|
109
111
|
raise StandardError, 'Skeem call stack empty!'
|
110
112
|
end
|
data/lib/skeem/s_expr_builder.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require_relative 'skm_pair'
|
3
|
+
require_relative 'skm_binding'
|
3
4
|
require_relative 's_expr_nodes'
|
4
5
|
|
5
6
|
module Skeem
|
@@ -60,8 +61,8 @@ module Skeem
|
|
60
61
|
|
61
62
|
# rule('definition' => 'LPAREN DEFINE IDENTIFIER expression RPAREN')
|
62
63
|
# .as 'definition'
|
63
|
-
def reduce_definition(_production,
|
64
|
-
|
64
|
+
def reduce_definition(_production, _range, _tokens, theChildren)
|
65
|
+
SkmBinding.new(theChildren[2], theChildren[3])
|
65
66
|
end
|
66
67
|
|
67
68
|
# rule('definition' => 'LPAREN DEFINE LPAREN IDENTIFIER def_formals RPAREN body RPAREN').as 'alt_definition'
|
@@ -69,7 +70,7 @@ module Skeem
|
|
69
70
|
def reduce_alt_definition(_production, aRange, _tokens, theChildren)
|
70
71
|
lmbd = SkmLambda.new(aRange, theChildren[4], theChildren[6])
|
71
72
|
# $stderr.puts lmbd.inspect
|
72
|
-
|
73
|
+
SkmBinding.new(theChildren[3], lmbd)
|
73
74
|
end
|
74
75
|
|
75
76
|
# rule('expression' => 'IDENTIFIER').as 'variable_reference'
|
@@ -245,6 +246,11 @@ module Skeem
|
|
245
246
|
def reduce_conditional(_production, aRange, _tokens, theChildren)
|
246
247
|
SkmCondition.new(aRange, theChildren[2], theChildren[3], theChildren[4])
|
247
248
|
end
|
249
|
+
|
250
|
+
# rule('assignment' => 'LPAREN SET! IDENTIFIER expression RPAREN').as 'assignment'
|
251
|
+
def reduce_assignment(_production, _range, _tokens, theChildren)
|
252
|
+
SkmUpdateBinding.new(theChildren[2], theChildren[3])
|
253
|
+
end
|
248
254
|
|
249
255
|
# rule('quasiquotation' => 'LPAREN QUASIQUOTE qq_template RPAREN').as 'quasiquotation'
|
250
256
|
def reduce_quasiquotation(_production, aRange, _tokens, theChildren)
|
data/lib/skeem/s_expr_nodes.rb
CHANGED
@@ -8,26 +8,17 @@ require_relative 'skm_unary_expression'
|
|
8
8
|
module Skeem
|
9
9
|
class SkmUndefined
|
10
10
|
include Singleton
|
11
|
-
|
11
|
+
|
12
12
|
def value
|
13
|
-
|
13
|
+
self
|
14
14
|
end
|
15
15
|
|
16
16
|
def ==(other)
|
17
|
-
|
18
|
-
|
19
|
-
result = case other
|
20
|
-
when Symbol
|
21
|
-
self.value == other
|
22
|
-
when String
|
23
|
-
self.value.to_s == other
|
24
|
-
else
|
25
|
-
raise StandardError, other.inspect
|
26
|
-
end
|
17
|
+
equal?(other)
|
27
18
|
end
|
28
|
-
|
19
|
+
|
29
20
|
private
|
30
|
-
|
21
|
+
|
31
22
|
def initialize
|
32
23
|
self.freeze
|
33
24
|
end
|
@@ -46,95 +37,13 @@ module Skeem
|
|
46
37
|
end
|
47
38
|
end
|
48
39
|
|
49
|
-
class SkmDefinition < SkmMultiExpression
|
50
|
-
attr_reader :variable
|
51
|
-
attr_reader :expression
|
52
|
-
|
53
|
-
def initialize(aPosition, aVariable, theExpression)
|
54
|
-
super(aPosition)
|
55
|
-
@variable = aVariable
|
56
|
-
@expression = theExpression
|
57
|
-
unless expression.kind_of?(SkmElement)
|
58
|
-
raise StandardError, "Bad definition"
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def evaluate(aRuntime)
|
63
|
-
var_key = variable.evaluate(aRuntime)
|
64
|
-
aRuntime.define(var_key, self)
|
65
|
-
case expression
|
66
|
-
when SkmLambda
|
67
|
-
result = expression.evaluate(aRuntime)
|
68
|
-
|
69
|
-
when SkmVariableReference
|
70
|
-
other_key = expression.variable.evaluate(aRuntime)
|
71
|
-
if var_key.value != other_key.value
|
72
|
-
entry = aRuntime.fetch(other_key)
|
73
|
-
result = expression.evaluate(aRuntime)
|
74
|
-
if entry.kind_of?(Primitive::PrimitiveProcedure)
|
75
|
-
@expression = entry
|
76
|
-
elsif entry.kind_of?(SkmDefinition)
|
77
|
-
if entry.expression.kind_of?(SkmLambda)
|
78
|
-
@expression = entry.expression
|
79
|
-
end
|
80
|
-
end
|
81
|
-
else
|
82
|
-
# INFINITE LOOP DANGER: definition of 'x' has a reference to 'x'!
|
83
|
-
# Way out: the lookup for the reference should start from outer
|
84
|
-
# environment.
|
85
|
-
env = aRuntime.pop
|
86
|
-
@expression = expression.evaluate(aRuntime)
|
87
|
-
aRuntime.push(env)
|
88
|
-
result = expression
|
89
|
-
end
|
90
|
-
else
|
91
|
-
result = self
|
92
|
-
end
|
93
|
-
|
94
|
-
result
|
95
|
-
end
|
96
|
-
|
97
|
-
def quasiquote(aRuntime)
|
98
|
-
quasi_var = variable.quasiquote(aRuntime)
|
99
|
-
quasi_expression = variable.quasiquote(aRuntime)
|
100
|
-
|
101
|
-
if quasi_var.equal?(variable) && quasi_expression.equal?(expression)
|
102
|
-
self
|
103
|
-
else
|
104
|
-
self.class.new(position, quasi_var, quasi_expression)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
# call method should only invoked when the expression is a SkmLambda
|
109
|
-
def call(aRuntime, aProcedureCall)
|
110
|
-
expr = expression
|
111
|
-
expr = expr.evaluate(aRuntime) if expr.kind_of?(SkmVariableReference)
|
112
|
-
unless [SkmLambda, Primitive::PrimitiveProcedure].include?(expr.class)
|
113
|
-
err_msg = "Expected a SkmLambda instead of #{expr.class}"
|
114
|
-
raise StandardError, err_msg
|
115
|
-
end
|
116
|
-
# $stderr.puts "SkmDefinition#call #{aProcedureCall.inspect}"
|
117
|
-
expr.call(aRuntime, aProcedureCall)
|
118
|
-
end
|
119
|
-
|
120
|
-
def inspect
|
121
|
-
result = inspect_prefix
|
122
|
-
result << variable.inspect
|
123
|
-
result << ', '
|
124
|
-
result << expression.inspect
|
125
|
-
result << inspect_suffix
|
126
|
-
result
|
127
|
-
end
|
128
|
-
|
129
|
-
def associations
|
130
|
-
[:variable, :expression]
|
131
|
-
end
|
132
|
-
end # class
|
133
|
-
|
134
40
|
class ProcedureCall < SkmMultiExpression
|
135
41
|
attr_reader :operator
|
136
42
|
attr_reader :operands
|
43
|
+
|
137
44
|
attr_accessor :call_site
|
45
|
+
|
46
|
+
# @return [FalseClass, TrueClass] True if all arguments are used by callee.
|
138
47
|
attr_accessor :operands_consumed
|
139
48
|
|
140
49
|
def initialize(aPosition, anOperator, theOperands)
|
@@ -154,48 +63,32 @@ module Skeem
|
|
154
63
|
end
|
155
64
|
|
156
65
|
def evaluate(aRuntime)
|
66
|
+
frame_change = false
|
157
67
|
aRuntime.push_call(self)
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
unless aRuntime.include?(var_key.value)
|
180
|
-
err = StandardError
|
181
|
-
key = var_key.kind_of?(SkmIdentifier) ? var_key.value : var_key
|
182
|
-
err_msg = "Unknown procedure '#{key}'"
|
183
|
-
raise err, err_msg
|
184
|
-
end
|
185
|
-
callee = aRuntime.environment.fetch(var_key.value)
|
186
|
-
end
|
187
|
-
unless var_key.nil?
|
188
|
-
# $stderr.puts "## In ProcCall #{var_key.value} #############"
|
189
|
-
# $stderr.puts 'operator: ' + operator.inspect
|
190
|
-
# $stderr.puts 'operands: ' + operands.inspect
|
191
|
-
# $stderr.puts "## CALL(#{var_key.value}) ###################"
|
192
|
-
# $stderr.puts 'callee: ' + callee.inspect
|
193
|
-
end
|
68
|
+
# $stderr.puts "\n Start of ProcedureCall#evaluate #{object_id.to_s(16)}"
|
69
|
+
# $stderr.puts " environment: #{aRuntime.environment.object_id.to_s(16)}, "
|
70
|
+
if aRuntime.environment && aRuntime.environment.parent
|
71
|
+
# $stderr.puts "Parent environment #{aRuntime.environment.parent.object_id.to_s(16)}, "
|
72
|
+
# $stderr.puts aRuntime.environment.inspect
|
73
|
+
end
|
74
|
+
#$stderr.puts ' operator: ' + operator.inspect
|
75
|
+
#$stderr.puts ' original operands: ' + operands.inspect
|
76
|
+
actuals = transform_operands(aRuntime)
|
77
|
+
#$stderr.puts ' transformed operands: ' + actuals.inspect
|
78
|
+
outcome, result = determine_callee(aRuntime)
|
79
|
+
if outcome == :callee
|
80
|
+
callee = result
|
81
|
+
# if callee.kind_of?(SkmLambda)
|
82
|
+
# aRuntime.push(callee.environment)
|
83
|
+
# frame_change = true
|
84
|
+
# end
|
85
|
+
# $stderr.puts ' callee: ' + callee.inspect
|
86
|
+
result = callee.call(aRuntime, actuals)
|
87
|
+
operands_consumed = true
|
88
|
+
# aRuntime.pop if frame_change
|
194
89
|
end
|
195
|
-
result = callee.call(aRuntime, self)
|
196
|
-
operands_consumed = true
|
197
90
|
aRuntime.pop_call
|
198
|
-
# $stderr.puts "
|
91
|
+
# $stderr.puts "\n End of ProcedureCall#evaluate #{object_id.to_s(16)}"
|
199
92
|
result
|
200
93
|
end
|
201
94
|
|
@@ -217,6 +110,78 @@ module Skeem
|
|
217
110
|
end
|
218
111
|
|
219
112
|
alias children operands
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def determine_callee(aRuntime)
|
117
|
+
case operator
|
118
|
+
when SkmIdentifier
|
119
|
+
callee = fetch_callee(aRuntime, operator)
|
120
|
+
when ProcedureCall
|
121
|
+
result = operator.evaluate(aRuntime)
|
122
|
+
# If child proc call consumes the parent's operand, then we're done
|
123
|
+
return [:result, result] unless result.callable? # if operands_consumed
|
124
|
+
callee = result
|
125
|
+
# callee = fetch_callee(aRuntime, result)
|
126
|
+
when Primitive::PrimitiveProcedure
|
127
|
+
callee = operator
|
128
|
+
when SkmLambda
|
129
|
+
callee = operator
|
130
|
+
else
|
131
|
+
result = operator.evaluate(aRuntime)
|
132
|
+
callee = fetch_callee(aRuntime, result)
|
133
|
+
end
|
134
|
+
|
135
|
+
[:callee, callee]
|
136
|
+
end
|
137
|
+
|
138
|
+
def fetch_callee(aRuntime, var_key)
|
139
|
+
begin
|
140
|
+
aRuntime.include?(var_key.value)
|
141
|
+
rescue NoMethodError => exc
|
142
|
+
# $stderr.puts "VVVVVVVVVVVVVVV"
|
143
|
+
# $stderr.puts 'var_key: ' + var_key.inspect
|
144
|
+
# $stderr.puts 'operator: ' + operator.inspect
|
145
|
+
# $stderr.puts 'operands: ' + operands.inspect
|
146
|
+
# $stderr.puts 'operands_consumed: ' + operands_consumed.inspect
|
147
|
+
# $stderr.puts "^^^^^^^^^^^^^^^"
|
148
|
+
raise exc
|
149
|
+
end
|
150
|
+
unless aRuntime.include?(var_key.value)
|
151
|
+
err = StandardError
|
152
|
+
key = var_key.kind_of?(SkmIdentifier) ? var_key.value : var_key
|
153
|
+
err_msg = "Unknown procedure '#{key}'"
|
154
|
+
# $stderr.puts aRuntime.inspect
|
155
|
+
# $stderr.puts aRuntime.environment.size.inspect
|
156
|
+
raise err, err_msg
|
157
|
+
end
|
158
|
+
callee = aRuntime.environment.fetch(var_key.value)
|
159
|
+
# $stderr.puts "## CALL(#{var_key.value}) ###################"
|
160
|
+
# $stderr.puts 'callee: ' + callee.inspect
|
161
|
+
# $stderr.puts 'operator: ' + operator.inspect
|
162
|
+
# $stderr.puts 'operands: ' + operands.inspect
|
163
|
+
|
164
|
+
callee
|
165
|
+
end
|
166
|
+
|
167
|
+
def transform_operands(aRuntime)
|
168
|
+
return [] if operands == SkmEmptyList.instance
|
169
|
+
actuals = operands.to_a
|
170
|
+
|
171
|
+
result = actuals.map do |actual|
|
172
|
+
case actual
|
173
|
+
when SkmVariableReference
|
174
|
+
aRuntime.fetch(actual.child)
|
175
|
+
else
|
176
|
+
actual.evaluate(aRuntime)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
result.nil? ? [] : result
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
|
220
185
|
end # class
|
221
186
|
|
222
187
|
class SkmCondition < SkmMultiExpression
|
@@ -331,6 +296,10 @@ module Skeem
|
|
331
296
|
end
|
332
297
|
end # class
|
333
298
|
|
299
|
+
|
300
|
+
|
301
|
+
require_relative 'skm_procedure_exec'
|
302
|
+
|
334
303
|
class SkmLambda < SkmMultiExpression
|
335
304
|
include DatumDSL
|
336
305
|
|
@@ -339,6 +308,7 @@ module Skeem
|
|
339
308
|
attr_reader :formals
|
340
309
|
attr_reader :definitions
|
341
310
|
attr_reader :sequence
|
311
|
+
attr_reader :environment
|
342
312
|
|
343
313
|
def initialize(aPosition, theFormals, aBody)
|
344
314
|
super(aPosition)
|
@@ -348,9 +318,12 @@ module Skeem
|
|
348
318
|
end
|
349
319
|
|
350
320
|
def evaluate(aRuntime)
|
351
|
-
# formals.evaluate(aRuntime)
|
352
321
|
self
|
353
322
|
end
|
323
|
+
|
324
|
+
def callable?
|
325
|
+
true
|
326
|
+
end
|
354
327
|
=begin
|
355
328
|
TODO
|
356
329
|
def quasiquote(aRuntime)
|
@@ -361,17 +334,28 @@ module Skeem
|
|
361
334
|
self.class.new(position, quasi_test, quasi_consequent, quasi_alternate)
|
362
335
|
end
|
363
336
|
=end
|
364
|
-
def call(aRuntime,
|
337
|
+
def call(aRuntime, theActuals)
|
338
|
+
set_cond_environment(aRuntime.environment) # Last chance for anonymous lambda
|
339
|
+
application = SkmProcedureExec.new(self)
|
340
|
+
application.run!(aRuntime, theActuals)
|
341
|
+
=begin
|
342
|
+
# $stderr.puts "Start of lambda #{object_id.to_s(16)}"
|
365
343
|
aRuntime.nest
|
344
|
+
# $stderr.puts ' Before bind_locals'
|
366
345
|
bind_locals(aRuntime, aProcedureCall)
|
346
|
+
# $stderr.puts ' After bind_locals'
|
367
347
|
# $stderr.puts 'LOCALS vvvvvvvvvvv'
|
368
348
|
# $stderr.puts aRuntime.environment.inspect
|
369
349
|
# $stderr.puts 'LOCALS ^^^^^^^^^^^'
|
370
350
|
result = evaluate_defs(aRuntime)
|
351
|
+
# $stderr.puts ' Before evaluate_sequence'
|
371
352
|
result = evaluate_sequence(aRuntime)
|
353
|
+
# $stderr.puts ' After evaluate_sequence'
|
372
354
|
aRuntime.unnest
|
373
|
-
|
355
|
+
# $stderr.puts " Result: #{result.inspect}"
|
356
|
+
# $stderr.puts "End of lambda #{object_id.to_s(16)}"
|
374
357
|
result
|
358
|
+
=end
|
375
359
|
end
|
376
360
|
|
377
361
|
def arity
|
@@ -385,10 +369,14 @@ module Skeem
|
|
385
369
|
alias eqv? equal?
|
386
370
|
alias skm_equal? equal?
|
387
371
|
|
372
|
+
def bound!(aFrame)
|
373
|
+
set_cond_environment(aFrame)
|
374
|
+
end
|
375
|
+
|
388
376
|
def inspect
|
389
|
-
result = inspect_prefix +
|
390
|
-
result <<
|
391
|
-
result <<
|
377
|
+
result = inspect_prefix + "@object_id=#{object_id.to_s(16)}, "
|
378
|
+
result << inspect_specific
|
379
|
+
result << inspect_suffix
|
392
380
|
result
|
393
381
|
end
|
394
382
|
|
@@ -396,18 +384,18 @@ module Skeem
|
|
396
384
|
[:formals, :definitions, :sequence]
|
397
385
|
end
|
398
386
|
|
399
|
-
|
400
|
-
|
401
|
-
def bind_locals(aRuntime, aProcedureCall)
|
402
|
-
actuals = aProcedureCall.operands.to_a
|
387
|
+
def bind_locals(aRuntime, theActuals)
|
388
|
+
actuals = theActuals
|
403
389
|
count_actuals = actuals.size
|
404
390
|
|
405
391
|
if (count_actuals < required_arity) ||
|
406
392
|
((count_actuals > required_arity) && !formals.variadic?)
|
407
|
-
|
393
|
+
# $stderr.puts "Error"
|
394
|
+
# $stderr.puts self.inspect
|
395
|
+
raise StandardError, msg_arity_mismatch(theActuals)
|
408
396
|
end
|
409
397
|
return if count_actuals.zero? && !formals.variadic?
|
410
|
-
bind_required_locals(aRuntime,
|
398
|
+
bind_required_locals(aRuntime, theActuals)
|
411
399
|
if formals.variadic?
|
412
400
|
variadic_part_raw = actuals.drop(required_arity)
|
413
401
|
variadic_part = variadic_part_raw.map do |actual|
|
@@ -422,14 +410,17 @@ module Skeem
|
|
422
410
|
end
|
423
411
|
variadic_arg_name = formals.formals.last
|
424
412
|
args_coll = SkmPair.create_from_a(variadic_part)
|
425
|
-
a_def = SkmDefinition.new(position, variadic_arg_name, args_coll)
|
413
|
+
# a_def = SkmDefinition.new(position, variadic_arg_name, args_coll)
|
414
|
+
a_def = SkmBinding.new(variadic_arg_name, args_coll)
|
415
|
+
a_def.evaluate(aRuntime)
|
416
|
+
aRuntime.add_binding(a_def.variable, a_def.value)
|
426
417
|
# $stderr.puts "Tef #{a_def.inspect}"
|
427
418
|
# $stderr.puts "Tef #{actuals.inspect}"
|
428
419
|
# $stderr.puts "Tef #{variadic_part.inspect}"
|
429
420
|
# $stderr.puts "Tef #{aProcedureCall.inspect}"
|
430
|
-
a_def.evaluate(aRuntime)
|
421
|
+
# a_def.evaluate(aRuntime)
|
431
422
|
end
|
432
|
-
aProcedureCall.operands_consumed = true
|
423
|
+
#aProcedureCall.operands_consumed = true
|
433
424
|
end
|
434
425
|
|
435
426
|
def evaluate_defs(aRuntime)
|
@@ -440,45 +431,54 @@ module Skeem
|
|
440
431
|
result = nil
|
441
432
|
if sequence
|
442
433
|
sequence.each do |cmd|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
unless aRuntime.caller(caller_index).operands_consumed
|
448
|
-
aRuntime.caller(caller_index).operands_consumed = true
|
449
|
-
result = cmd.call(aRuntime, aRuntime.caller(caller_index))
|
450
|
-
break
|
451
|
-
end
|
452
|
-
caller_index -= 1
|
453
|
-
end
|
454
|
-
else
|
455
|
-
begin
|
434
|
+
begin
|
435
|
+
if cmd.kind_of?(SkmLambda)
|
436
|
+
result = cmd.dup_cond(aRuntime)
|
437
|
+
else
|
456
438
|
result = cmd.evaluate(aRuntime)
|
457
|
-
rescue NoMethodError => exc
|
458
|
-
$stderr.puts self.inspect
|
459
|
-
$stderr.puts sequence.inspect
|
460
|
-
$stderr.puts cmd.inspect
|
461
|
-
raise exc
|
462
439
|
end
|
440
|
+
rescue NoMethodError => exc
|
441
|
+
$stderr.puts self.inspect
|
442
|
+
$stderr.puts sequence.inspect
|
443
|
+
$stderr.puts cmd.inspect
|
444
|
+
raise exc
|
463
445
|
end
|
464
446
|
end
|
465
447
|
end
|
466
448
|
|
467
449
|
result
|
468
450
|
end
|
451
|
+
|
452
|
+
def dup_cond(aRuntime)
|
453
|
+
if environment
|
454
|
+
result = self
|
455
|
+
else
|
456
|
+
twin = self.dup
|
457
|
+
twin.set_cond_environment(aRuntime.environment)
|
458
|
+
result = twin
|
459
|
+
end
|
460
|
+
|
461
|
+
result
|
462
|
+
end
|
463
|
+
|
464
|
+
def set_cond_environment(theFrame)
|
465
|
+
# $stderr.puts "Lambda #{object_id.to_s(16)}, env [#{environment.object_id.to_s(16)}]"
|
466
|
+
# $stderr.puts " Runtime environment: #{theFrame.object_id.to_s(16)}"
|
467
|
+
# $stderr.puts " Called from #{caller(1, 1)}"
|
468
|
+
raise StandardError unless theFrame.kind_of?(SkmFrame)
|
469
|
+
unless environment
|
470
|
+
@environment = theFrame
|
471
|
+
self.freeze
|
472
|
+
# $stderr.puts " Lambda's environment updated!"
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
private
|
469
477
|
|
470
478
|
# Purpose: bind each formal from lambda to an actual value from the call
|
471
|
-
def bind_required_locals(aRuntime,
|
479
|
+
def bind_required_locals(aRuntime, theActuals)
|
472
480
|
max_index = required_arity - 1
|
473
|
-
|
474
|
-
when SkmPair
|
475
|
-
actuals = aProcedureCall.operands.to_a
|
476
|
-
when SkmEmptyList
|
477
|
-
actuals = []
|
478
|
-
else
|
479
|
-
raise StandardError, "Unsupported type of operand list #{aProcedureCall.operands.inspect}"
|
480
|
-
actuals = aProcedureCall.operands.members
|
481
|
-
end
|
481
|
+
actuals = theActuals
|
482
482
|
formal_names = formals.formals.map(&:value)
|
483
483
|
|
484
484
|
formals.formals.each_with_index do |arg_name, index|
|
@@ -496,25 +496,40 @@ module Skeem
|
|
496
496
|
unless arg.kind_of?(SkmElement)
|
497
497
|
arg = to_datum(arg)
|
498
498
|
end
|
499
|
-
a_def = SkmDefinition.new(position, arg_name, arg)
|
500
|
-
|
499
|
+
# a_def = SkmDefinition.new(position, arg_name, arg)
|
500
|
+
a_def = SkmBinding.new(arg_name, arg)
|
501
|
+
# $stderr.puts "Lambda #{object_id.to_s(16)}"
|
501
502
|
# $stderr.puts "LOCAL #{arg_name.value} #{arg.inspect}"
|
502
503
|
if arg.kind_of?(SkmVariableReference) && !formal_names.include?(arg.value)
|
503
|
-
aRuntime.
|
504
|
+
aRuntime.add_binding(arg_name, a_def)
|
504
505
|
else
|
505
|
-
a_def.evaluate(aRuntime)
|
506
|
+
aRuntime.add_binding(a_def.variable, a_def.evaluate(aRuntime))
|
506
507
|
end
|
507
508
|
break if index >= max_index
|
508
509
|
end
|
509
510
|
end
|
510
511
|
|
511
|
-
def msg_arity_mismatch(
|
512
|
+
def msg_arity_mismatch(actuals)
|
512
513
|
# *** ERROR: wrong number of arguments for #<closure morph> (required 2, got 1)
|
513
|
-
msg1 = "Wrong number of arguments for procedure
|
514
|
-
count_actuals =
|
514
|
+
msg1 = "Wrong number of arguments for procedure "
|
515
|
+
count_actuals = actuals.size
|
515
516
|
msg2 = "(required #{required_arity}, got #{count_actuals})"
|
516
517
|
msg1 + msg2
|
517
518
|
end
|
519
|
+
|
520
|
+
def inspect_specific
|
521
|
+
#result = "@environment #{environment.object_id.to_s(16)}, "
|
522
|
+
result = ''
|
523
|
+
if environment && environment.parent
|
524
|
+
result << "Parent environment #{environment.parent.object_id.to_s(16)}, "
|
525
|
+
result << environment.inspect
|
526
|
+
end
|
527
|
+
result << '@formals ' + formals.inspect + ', '
|
528
|
+
result << '@definitions ' + definitions.inspect + ', '
|
529
|
+
result << '@sequence ' + sequence.inspect + inspect_suffix
|
530
|
+
|
531
|
+
result
|
532
|
+
end
|
518
533
|
end # class
|
519
534
|
end # module
|
520
535
|
# End of file
|