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