heist 0.1.0 → 0.2.0
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.
- data/History.txt +17 -0
- data/Manifest.txt +23 -19
- data/README.txt +84 -52
- data/lib/builtin/library.scm +208 -10
- data/lib/builtin/primitives.rb +154 -92
- data/lib/builtin/syntax.scm +22 -5
- data/lib/heist.rb +49 -17
- data/lib/parser/nodes.rb +47 -24
- data/lib/parser/ruby.rb +29 -0
- data/lib/parser/scheme.rb +455 -143
- data/lib/parser/scheme.tt +23 -5
- data/lib/repl.rb +19 -16
- data/lib/runtime/binding.rb +24 -2
- data/lib/runtime/callable/continuation.rb +23 -2
- data/lib/runtime/callable/function.rb +122 -21
- data/lib/runtime/callable/macro.rb +169 -123
- data/lib/runtime/callable/macro/expansion.rb +137 -2
- data/lib/runtime/callable/macro/matches.rb +125 -41
- data/lib/runtime/callable/macro/tree.rb +141 -0
- data/lib/runtime/callable/syntax.rb +44 -0
- data/lib/runtime/data/cons.rb +234 -0
- data/lib/runtime/data/expression.rb +15 -6
- data/lib/runtime/data/identifier.rb +19 -2
- data/lib/runtime/frame.rb +102 -35
- data/lib/runtime/runtime.rb +44 -19
- data/lib/runtime/scope.rb +145 -30
- data/lib/runtime/stack.rb +103 -1
- data/lib/runtime/stackless.rb +48 -6
- data/test/arithmetic.scm +11 -2
- data/test/continuations.scm +16 -2
- data/test/equivalence.scm +34 -0
- data/test/functional.scm +4 -0
- data/test/lists.scm +78 -0
- data/test/macro-helpers.scm +1 -0
- data/test/macros.scm +111 -24
- data/test/numbers.scm +30 -8
- data/test/test_heist.rb +67 -12
- metadata +25 -21
- data/lib/builtin/syntax.rb +0 -166
- data/lib/runtime/callable/macro/splice.rb +0 -56
- data/lib/runtime/data/list.rb +0 -36
@@ -0,0 +1,44 @@
|
|
1
|
+
module Heist
|
2
|
+
class Runtime
|
3
|
+
|
4
|
+
# The +Syntax+ class is used to model built-in special forms. +Syntax+
|
5
|
+
# functions are universally implemented in Ruby; user-defined special
|
6
|
+
# forms are represented using +Macro+. +Syntax+ is very simple: its
|
7
|
+
# body is a Ruby block that accepts a +Scope+ and a +Cons+ list of
|
8
|
+
# the expression following the special form, and the +Syntax+ class
|
9
|
+
# does not automatically evaluate any parameters. It is up to the
|
10
|
+
# Ruby code implementing the syntax to decide what to evaluate. For
|
11
|
+
# example, here's a couple of implementations for Scheme's <tt>(if)</tt>
|
12
|
+
# and <tt>(set!)</tt>.
|
13
|
+
#
|
14
|
+
# env = Scope.new
|
15
|
+
#
|
16
|
+
# # e.g. (set! x (+ 9 4))
|
17
|
+
# env['set!'] = Syntax.new(env) do |scope, cells|
|
18
|
+
# value = Heist.evaluate(cells.cdr.car, scope)
|
19
|
+
# scope.set!(cells.car, value)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # e.g. (if (> 6 3) 'yes 'no)
|
23
|
+
# env['if'] = Syntax.new(env) do |scope, cells|
|
24
|
+
# which = Heist.evaluate(cells.car, scope) ? cells.cdr : cells.cdr.cdr
|
25
|
+
# Heist.evaluate(which.car, scope)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
class Syntax < Function
|
29
|
+
|
30
|
+
# Calls the Ruby implementation of the +Syntax+ and returns the result.
|
31
|
+
def call(scope, cells)
|
32
|
+
@body.call(scope, cells)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a string placeholder for the +Syntax+, containing its
|
36
|
+
# name if it has one.
|
37
|
+
def to_s
|
38
|
+
"#<syntax:#{ @name }>"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,234 @@
|
|
1
|
+
module Heist
|
2
|
+
class Runtime
|
3
|
+
|
4
|
+
# +Cons+ is the basic data structure element in Scheme. A +Cons+ is an
|
5
|
+
# object with two pointers to other objects, the pointers being called
|
6
|
+
# the +car+ and the +cdr+. You can typically think of this type of
|
7
|
+
# object as representing a pair of values, though not all +Cons+ objects
|
8
|
+
# are pairs according to the <tt>(pair?)</tt> procedure; specifically
|
9
|
+
# +NULL+ is not considered a pair.
|
10
|
+
#
|
11
|
+
# +Cons+ objects can be joined together to form lists; a list is either
|
12
|
+
# the empty list or a +Cons+ whose +cdr+ is a list. That is, a list is
|
13
|
+
# chain of +Cons+ pairs joined by their +cdr+ fields, whose +car+ fields
|
14
|
+
# hold the elements of the list. A list is considered 'proper' if its
|
15
|
+
# final +Cons+ is +NULL+, and improper otherwise. Some examples:
|
16
|
+
#
|
17
|
+
# Proper list: (1 2 3) Improper list: (1 2 3 . 4)
|
18
|
+
#
|
19
|
+
# # # # = cons
|
20
|
+
# / \\ / \\ / = car
|
21
|
+
# 1 # 1 # \\ = cdr
|
22
|
+
# / \\ / \\ () = NULL
|
23
|
+
# 2 # 2 #
|
24
|
+
# / \\ / \\
|
25
|
+
# 3 () 3 4
|
26
|
+
#
|
27
|
+
# +Cons+ objects are +Enumerable+, though always keep in mind that a
|
28
|
+
# +Cons+ does not 'contain' a whole list, it contains one value and a
|
29
|
+
# pointer to the rest of the list. Iterating on a +Cons+ involves
|
30
|
+
# walking this object graph.
|
31
|
+
|
32
|
+
class Cons
|
33
|
+
include Enumerable
|
34
|
+
include Expression
|
35
|
+
|
36
|
+
attr_accessor :car, :cdr
|
37
|
+
|
38
|
+
# +NULL+ is a special +Cons+ instance representing the empty list, which
|
39
|
+
# is used as a nil value in Scheme. The empty list is a singleton: there
|
40
|
+
# is only ever one empty list in memory, and the Scheme expressions
|
41
|
+
# <tt>'()</tt> and <tt>(list)</tt> always return the same object. +NULL+
|
42
|
+
# is frozen, and its +car+ and +cdr+ are pointers back to itself.
|
43
|
+
NULL = self.new
|
44
|
+
NULL.car = NULL.cdr = NULL
|
45
|
+
|
46
|
+
# +NULL+ is only equal to itself.
|
47
|
+
def NULL.==(other); equal?(other); end
|
48
|
+
NULL.freeze
|
49
|
+
|
50
|
+
cadr_combos = (1..4).map { |n|
|
51
|
+
(0...(2**n)).map do |x|
|
52
|
+
'c' + ("%0#{n}d" % x.to_s(2)).gsub('0', 'a').gsub('1', 'd') + 'r'
|
53
|
+
end
|
54
|
+
}.flatten
|
55
|
+
|
56
|
+
# An array of all the c[ad]+r functions supported by Heist
|
57
|
+
ACCESSORS = cadr_combos
|
58
|
+
|
59
|
+
class << self
|
60
|
+
# Creates a new list from the elements of the enumerable object +enum+,
|
61
|
+
# and returns the +Cons+ that forms the head of the list. If +enum+ is
|
62
|
+
# empty, +NULL+ is returned. +construct+ optionally takes a block that
|
63
|
+
# can be used to transform each value as the list is constructed:
|
64
|
+
#
|
65
|
+
# Cons.construct([1,2,3,4]) { |x| x*x }
|
66
|
+
# #=> (1 4 9 16)
|
67
|
+
#
|
68
|
+
# The method also takes an optional second parameter +linking+, which,
|
69
|
+
# if set to true, makes each +car+ element aware of the +Cons+ that
|
70
|
+
# holds it. This is used when inlining macro expansions, where
|
71
|
+
# expressions need to replace themselves in their parent expression.
|
72
|
+
#
|
73
|
+
# Note that this method copies improper lists correctly, though
|
74
|
+
# iteration over a +Cons+ list does not include the tail value.
|
75
|
+
#
|
76
|
+
def construct(enum, linking = false, &block)
|
77
|
+
return NULL if enum.nil?
|
78
|
+
root, last = nil, nil
|
79
|
+
enum.each do |value|
|
80
|
+
value = block.call(value) if block_given?
|
81
|
+
pair = self.new(value)
|
82
|
+
pair.hosts(value) if linking
|
83
|
+
root ||= pair
|
84
|
+
last.cdr = pair if last
|
85
|
+
last = pair.tail
|
86
|
+
end
|
87
|
+
last.cdr = enum.tail.cdr if last and Cons === enum
|
88
|
+
root || NULL
|
89
|
+
end
|
90
|
+
alias :[] :construct
|
91
|
+
end
|
92
|
+
|
93
|
+
# A +Cons+ is initialized using a +car+ and a +cdr+ value.
|
94
|
+
def initialize(car = nil, cdr = nil)
|
95
|
+
self.car = car
|
96
|
+
self.cdr = cdr
|
97
|
+
end
|
98
|
+
|
99
|
+
ACCESSORS[2..-1].each do |accsr|
|
100
|
+
class_eval <<-EOS
|
101
|
+
def #{ accsr }
|
102
|
+
#{ accsr.scan(/[ad]/).reverse.map { |s| "c#{s}r" } * '.' }
|
103
|
+
end
|
104
|
+
EOS
|
105
|
+
end
|
106
|
+
|
107
|
+
# Sets the +car+ of the receiving +Cons+, unless it is frozen. If the
|
108
|
+
# given value is +nil+, +NULL+ is used instead.
|
109
|
+
def car=(car)
|
110
|
+
raise ImmutableError.new("Cannot modify constant value") if frozen?
|
111
|
+
@car = car.nil? ? NULL : car
|
112
|
+
end
|
113
|
+
|
114
|
+
# Sets the +cdr+ of the receiving +Cons+, unless it is frozen. If the
|
115
|
+
# given value is +nil+, +NULL+ is used instead.
|
116
|
+
def cdr=(cdr)
|
117
|
+
raise ImmutableError.new("Cannot modify constant value") if frozen?
|
118
|
+
@cdr = cdr.nil? ? NULL : cdr
|
119
|
+
end
|
120
|
+
|
121
|
+
# Recursively freezes the +Cons+ and any conses it points to. This is
|
122
|
+
# used to prevent quoted list constants from being modified since they
|
123
|
+
# are part of the parse tree: quoting a list does not allocate a new
|
124
|
+
# object so changing a quoted list would have undesirable side effects
|
125
|
+
# on any other references to the list.
|
126
|
+
def freeze!
|
127
|
+
return if null?
|
128
|
+
[@car, @cdr].each { |slot| slot.freeze! if slot.respond_to?(:freeze!) }
|
129
|
+
freeze
|
130
|
+
end
|
131
|
+
|
132
|
+
# Sets the parent pair of +value+ to the receiver. Some +car+ values
|
133
|
+
# need a reference back to their containing +Cons+ for concerns such
|
134
|
+
# as macro expansion inlining.
|
135
|
+
def hosts(value)
|
136
|
+
value.parent = self if Expression === value and value != NULL
|
137
|
+
end
|
138
|
+
|
139
|
+
# Creates and returns a copy of the +Cons+ list, taking an optional
|
140
|
+
# mapping block (see +construct+).
|
141
|
+
def clone(&block)
|
142
|
+
Cons.construct(self, &block)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Iterates over each +Cons+ in a list, yielding the +car+ value for
|
146
|
+
# each +Cons+. Returns the final +Cons+ in the list, from where we
|
147
|
+
# can inspect the tail's +cdr+ to see if the list if proper or not.
|
148
|
+
# The block is optional and the method is aliased as +tail+, so to
|
149
|
+
# get the tail value of an improper list you'd call <tt>list.tail.cdr</tt>.
|
150
|
+
def each
|
151
|
+
pair, tail = self, NULL
|
152
|
+
while Cons === pair and not pair.null?
|
153
|
+
yield(pair.car) if block_given?
|
154
|
+
tail = pair
|
155
|
+
pair = pair.cdr
|
156
|
+
end
|
157
|
+
tail
|
158
|
+
end
|
159
|
+
alias :tail :each
|
160
|
+
|
161
|
+
# Returns the length of the list whose head is the receiving +Cons+.
|
162
|
+
# If the list is improper an exception is raised.
|
163
|
+
def length
|
164
|
+
size = 0
|
165
|
+
tail = each { |value| size += 1 }
|
166
|
+
raise TypeError.new("Cannot get the length of improper list #{self}") unless tail.cdr == NULL
|
167
|
+
size
|
168
|
+
end
|
169
|
+
alias :size :length
|
170
|
+
|
171
|
+
# Returns +true+ iff +other+ is equal to the receiver, that is to
|
172
|
+
# say that +other+ is a +Cons+ with the same +car+ and +cdr+ as the
|
173
|
+
# receiving +Cons+.
|
174
|
+
def ==(other)
|
175
|
+
return false unless Cons === other
|
176
|
+
return false if NULL == other
|
177
|
+
@car == other.car and @cdr == other.cdr
|
178
|
+
end
|
179
|
+
|
180
|
+
# If the receiving list contains +Binding+ objects, said objects are
|
181
|
+
# forced to evaluate themselves to populate the list.
|
182
|
+
def force!
|
183
|
+
return self unless Binding === @car
|
184
|
+
pair = self
|
185
|
+
while not pair.null?
|
186
|
+
pair.car = pair.car.force!
|
187
|
+
pair = pair.cdr
|
188
|
+
end
|
189
|
+
self
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns +true+ iff the receiving +Cons+ is the head of a proper list.
|
193
|
+
def list?
|
194
|
+
tail.cdr == NULL; end
|
195
|
+
|
196
|
+
# Returns +true+ iff the receiving +Cons+ is a valid pair.
|
197
|
+
def pair?
|
198
|
+
not null?; end
|
199
|
+
|
200
|
+
# Returns +true+ iff the receiving +Cons+ is the empty list.
|
201
|
+
def null?
|
202
|
+
self == NULL; end
|
203
|
+
|
204
|
+
# Returns an array representation of the list. If +deep+ is +true+,
|
205
|
+
# the array conversion is performed recursively.
|
206
|
+
def to_a(deep = false)
|
207
|
+
map { |cell| deep && Cons === cell ? cell.to_a : cell }
|
208
|
+
end
|
209
|
+
|
210
|
+
# Returns a pure Ruby representation of the list, with any Heist
|
211
|
+
# specific objects converted to basic Ruby equivalents.
|
212
|
+
def to_ruby
|
213
|
+
map { |cell| cell.respond_to?(:to_ruby) ? cell.to_ruby : cell }
|
214
|
+
end
|
215
|
+
|
216
|
+
# Returns a Scheme-style string representation of the list.
|
217
|
+
def to_s
|
218
|
+
strings = []
|
219
|
+
tail = each { |value|
|
220
|
+
strings << case value
|
221
|
+
when String then value.inspect
|
222
|
+
when Symbol then "'#{value}"
|
223
|
+
else value
|
224
|
+
end
|
225
|
+
}.cdr
|
226
|
+
'(' + (strings * ' ') +
|
227
|
+
(tail == NULL ? '' : ' . ' + tail.to_s) + ')'
|
228
|
+
end
|
229
|
+
alias :inspect :to_s
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
@@ -1,18 +1,27 @@
|
|
1
1
|
module Heist
|
2
2
|
class Runtime
|
3
3
|
|
4
|
+
# Classes may mix in the +Expression+ module to signal that they represent
|
5
|
+
# sections of code that may be evaluated. This is mostly a flag module
|
6
|
+
# and does not provide much in the way of extensibility, since the evaluator
|
7
|
+
# needs to know how to execute all the kinds of expressions you want it
|
8
|
+
# to deal with (for reasons such as tail recursion, expressions are not
|
9
|
+
# responsible for evaluating themselves).
|
4
10
|
module Expression
|
5
|
-
|
6
|
-
|
7
|
-
def exists_at!(parent, index)
|
8
|
-
@parent, @index = parent, index
|
9
|
-
end
|
11
|
+
attr_accessor :parent
|
10
12
|
|
13
|
+
# Replaces the receiver with +expression+ in the receiver's parent
|
14
|
+
# expression. This is used as part of the macro expansion process.
|
11
15
|
def replace(expression)
|
12
16
|
return unless @parent
|
13
|
-
@parent
|
17
|
+
@parent.car = expression
|
18
|
+
@parent.hosts(expression)
|
14
19
|
end
|
15
20
|
|
21
|
+
# Returns the result of evaluating the receiving +Expression+ in the
|
22
|
+
# given +scope+. Works by pushing the receiver and scope onto the
|
23
|
+
# runtime stack (could be a +Stack+ or +Stackless+), which then
|
24
|
+
# evaluates the expression using a trampoline.
|
16
25
|
def eval(scope)
|
17
26
|
scope.runtime.stack << Frame.new(self, scope)
|
18
27
|
end
|
@@ -1,17 +1,34 @@
|
|
1
1
|
module Heist
|
2
2
|
class Runtime
|
3
3
|
|
4
|
+
# An +Identifier+ is used to represent any name that appears in Scheme
|
5
|
+
# code. We might throw it away and use symbols at some point. I had this
|
6
|
+
# idea for storing metadata on +Identifier+ objects to speed things up
|
7
|
+
# but it pretty much came to nothing. Its one saving grace is that it
|
8
|
+
# needs to be treated as an +Expression+ class and I don't want to
|
9
|
+
# modify core Ruby classes.
|
4
10
|
class Identifier
|
5
11
|
include Expression
|
6
12
|
|
7
13
|
extend Forwardable
|
8
|
-
def_delegators(:@metadata, :[], :[]=)
|
9
14
|
def_delegators(:@name, :to_s)
|
10
15
|
alias :inspect :to_s
|
11
16
|
|
17
|
+
# An +Identifier+ is initialized using a string that becomes the
|
18
|
+
# name of the identifier.
|
12
19
|
def initialize(name)
|
13
20
|
@name = name.to_s
|
14
|
-
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns +true+ if the receiver has the same name as the argument.
|
24
|
+
def ==(other)
|
25
|
+
Identifier === other and @name == other.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a raw Ruby representation of the identifier, for which
|
29
|
+
# we use symbols.
|
30
|
+
def to_ruby
|
31
|
+
@name.to_s.to_sym
|
15
32
|
end
|
16
33
|
end
|
17
34
|
|
data/lib/runtime/frame.rb
CHANGED
@@ -1,115 +1,182 @@
|
|
1
1
|
module Heist
|
2
2
|
class Runtime
|
3
3
|
|
4
|
+
# +Frame+ is used by the +Stack+ class to encapsulate a single +Expression+
|
5
|
+
# bound to a +Scope+. +Frame+ objects evaluate expressions one sub-expression
|
6
|
+
# at a time, providing fine-grained opportunity for inspecting intermediate
|
7
|
+
# results and supporting the Scheme notion of continuations.
|
8
|
+
#
|
9
|
+
# The state of a +Frame+ is held in five variables: <tt>@expression</tt>,
|
10
|
+
# <tt>@scope</tt>, <tt>@values</tt>, <tt>@current</tt> and <tt>@complete</tt>.
|
11
|
+
# <tt>@expression</tt> is the +Expression+ object the +Frame+ is evaluating,
|
12
|
+
# and <tt>@scope</tt> is a +Scope+ object in which to evaluate it. <tt>@values</tt>
|
13
|
+
# is a copy of <tt>@expression</tt> that is used to store the values returned
|
14
|
+
# by the subexpressions; it represents the partially evaluated state of the
|
15
|
+
# expression for use when saving a +Stack+ when creating a +Continuation+.
|
16
|
+
# <tt>@current</tt> is a pointer to the subexpression that is to be evaluated
|
17
|
+
# next, and <tt>@complete</tt> is a boolean that indicates whether all the
|
18
|
+
# required subexpressions have been evaluated.
|
19
|
+
#
|
20
|
+
# See +Stack+ for a fuller explanation of how expressions are evaluated.
|
21
|
+
#
|
4
22
|
class Frame
|
5
23
|
attr_reader :expression, :scope
|
6
24
|
|
25
|
+
# A +Frame+ is initialized using an +Expression+ and a +Scope+, which
|
26
|
+
# are simply stored as instance variables. No evaluation takes place
|
27
|
+
# upon initialization.
|
7
28
|
def initialize(expression, scope)
|
8
29
|
reset!(expression)
|
9
30
|
@scope = scope
|
10
31
|
end
|
11
32
|
|
33
|
+
# Returns +true+ iff the +Frame+ has completed evaluating its expression.
|
12
34
|
def complete?
|
13
35
|
@complete
|
14
36
|
end
|
15
37
|
|
38
|
+
# Processes a single subexpression from the +Frame+ and returns the
|
39
|
+
# result. If all the required subexpressions have been evaluated, we
|
40
|
+
# call the resulting function with the arguments and return the result
|
41
|
+
# of that call.
|
16
42
|
def process!
|
17
43
|
case @expression
|
18
44
|
|
19
|
-
when
|
20
|
-
|
45
|
+
when Cons then
|
46
|
+
# If we have evaluated all the subexpressions, or we have a
|
47
|
+
# +Syntax+ value first in the list, we can make a function call
|
48
|
+
# and return its result.
|
49
|
+
function = @values.car
|
50
|
+
if Syntax === function or @current.null?
|
21
51
|
@complete = true
|
22
|
-
|
23
|
-
|
24
|
-
|
52
|
+
raise SyntaxError.new("Invalid expression: #{@expression}") unless Function === function
|
53
|
+
result = function.call(@scope, @values.cdr)
|
54
|
+
|
55
|
+
# If the result of the call is a macro expansion, inline it
|
56
|
+
# and set its expression as the new expression for the frame
|
25
57
|
return result unless Macro::Expansion === result
|
26
58
|
return reset!(result.expression, true)
|
27
59
|
end
|
28
60
|
|
61
|
+
# Otherwise, we still have work to do on the subexpressions, so
|
62
|
+
# we evaluate the next one in the list.
|
29
63
|
stack = @scope.runtime.stack
|
30
|
-
stack << Frame.new(@
|
64
|
+
stack << Frame.new(@current.car, @scope)
|
31
65
|
|
66
|
+
# If the expression is an +Identifier+, just look it up.
|
32
67
|
when Identifier then
|
33
68
|
@complete = true
|
34
69
|
@scope[@expression]
|
35
70
|
|
71
|
+
# Otherwise, the expression is just data so return it.
|
36
72
|
else
|
37
73
|
@complete = true
|
38
74
|
Heist.evaluate(@expression, @scope)
|
39
75
|
end
|
40
76
|
end
|
41
77
|
|
42
|
-
|
43
|
-
|
78
|
+
# Returns a copy of the +Frame+. The <tt>@values</tt> variable is also
|
79
|
+
# copied by this method so that state changes do not transfer from the
|
80
|
+
# copy to the original.
|
81
|
+
def clone
|
82
|
+
copy, values = super, @values.clone
|
44
83
|
copy.instance_eval { @values = values }
|
45
84
|
copy
|
46
85
|
end
|
47
86
|
|
87
|
+
# Fills the hole corresponding to +subexpr+ in the receiving +Frame+ with
|
88
|
+
# +value+. When a hole is filled, the <tt>@current</tt> pointer is moved
|
89
|
+
# to the next subexpression that needs evaluating.
|
48
90
|
def fill!(subexpr, value)
|
49
|
-
|
50
|
-
|
51
|
-
|
91
|
+
epair, vpair = @expression, @values
|
92
|
+
while Cons === epair and not epair.null?
|
93
|
+
if epair.car.equal?(subexpr)
|
94
|
+
vpair.car = value
|
95
|
+
@current = epair.cdr
|
96
|
+
end
|
97
|
+
epair, vpair = epair.cdr, vpair.cdr
|
98
|
+
end
|
52
99
|
end
|
53
100
|
|
101
|
+
# Sets the replacement target for the receiving +Frame+ to the given
|
102
|
+
# +expression+. When the receiving frame returns, it will fill the
|
103
|
+
# hole corresponding to the +expression+ in the previous +Frame+ on
|
104
|
+
# the +Stack+.
|
54
105
|
def replaces(expression)
|
55
106
|
@target = expression
|
56
107
|
end
|
57
108
|
|
109
|
+
# Returns the +Expression+ that is the replacement target for the
|
110
|
+
# previous +Frame+ on the +Stack+. When the receiving +Frame+ completes,
|
111
|
+
# the returned value will replace the return value of this method in
|
112
|
+
# the previous +Frame+. The +target+ of a +Frame+ is its <tt>@expression</tt>
|
113
|
+
# by default, but tail calls require us to change the target as, when
|
114
|
+
# a tail call returns, the +Frame+ that originated it will no longer
|
115
|
+
# by on the stack so +replaces+ is used to change the replacement
|
116
|
+
# target for the tail call.
|
58
117
|
def target
|
59
118
|
@target || @expression
|
60
119
|
end
|
61
120
|
|
62
121
|
private
|
63
122
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
@data[i] = @values[index] if index
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
123
|
+
# Resets the +Expression+ stored in the +Frame+. If the second argument
|
124
|
+
# is given as +true+, the given +expression+ replaces the previously
|
125
|
+
# stored expression in the source tree; this is used to inline macro
|
126
|
+
# expansions. Also resets the <tt>@values</tt> list to be a copy of the
|
127
|
+
# new +Expression+.
|
73
128
|
def reset!(expression, replace = false)
|
74
129
|
@expression.replace(expression) if replace
|
75
130
|
@expression = expression
|
76
|
-
@
|
77
|
-
@
|
131
|
+
@current = expression
|
132
|
+
@values = (Cons === expression) ? expression.clone : nil
|
78
133
|
@complete = false
|
79
134
|
end
|
80
135
|
end
|
81
136
|
|
137
|
+
# +Body+ is a subclass of +Frame+, used for evaluating +Function+ bodies.
|
138
|
+
# Instead of providing break points between subexpressions in a single
|
139
|
+
# expression, it provides break points between whole expressions inside
|
140
|
+
# a Scheme procedure. (A 'break point' is a point in code execution during
|
141
|
+
# which +Stack+ can inspect the last value returned and decide whether to
|
142
|
+
# continue the current +Frame+ or switch to some other action.)
|
82
143
|
class Body < Frame
|
144
|
+
|
145
|
+
# A +Body+ is initialized using a +Cons+ list containing a list of
|
146
|
+
# <tt>Expression</tt>s that make up the body of a +Function+, and a
|
147
|
+
# +Scope+ in which these expressions are to be evaluated.
|
83
148
|
def initialize(expressions, scope)
|
84
149
|
@expression = expressions
|
85
150
|
@scope = scope
|
86
151
|
@values = []
|
87
|
-
@index = 0
|
88
152
|
end
|
89
153
|
|
154
|
+
# Returns +true+ iff the +Body+ has evaluated all the expressions.
|
90
155
|
def complete?
|
91
|
-
@
|
156
|
+
@expression.null?
|
92
157
|
end
|
93
158
|
|
159
|
+
# Processes the next remaining +Expression+ from the +Function+ body,
|
160
|
+
# returning the result for inspection by the +Stack+.
|
94
161
|
def process!
|
95
|
-
expression = @expression
|
96
|
-
|
97
|
-
|
98
|
-
|
162
|
+
expression = @expression.car
|
163
|
+
|
164
|
+
# Increment before evaluating the expression so that when a
|
165
|
+
# continuation is saved we resume from the following statement
|
166
|
+
@expression = @expression.cdr
|
99
167
|
|
100
|
-
|
168
|
+
# Return the final expression as a +Frame+ to enable tail calls
|
169
|
+
return Frame.new(expression, @scope) if complete?
|
101
170
|
|
171
|
+
# For all non-tail calls, evaluate the expression and return the value
|
102
172
|
stack = @scope.runtime.stack
|
103
173
|
stack << Frame.new(expression, @scope)
|
104
174
|
stack.value
|
105
175
|
end
|
106
176
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
def to_s
|
112
|
-
@expression.map { |e| e.to_s } * ' '
|
177
|
+
# Do-nothing override of <tt>Frame#fill!</tt>. +Function+ bodies do not
|
178
|
+
# need to remember the return value of each expression.
|
179
|
+
def fill!(*args)
|
113
180
|
end
|
114
181
|
end
|
115
182
|
|