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
data/lib/runtime/runtime.rb
CHANGED
@@ -1,59 +1,84 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
1
|
module Heist
|
2
|
+
|
3
|
+
# +Runtime+ objects represent instances of the Heist runtime environment.
|
4
|
+
# Each +Runtime+ defines a top-level +Scope+, into which are injected
|
5
|
+
# the standard set of primitive functions and special forms as defined
|
6
|
+
# in <tt>lib/builtin</tt>.
|
7
|
+
#
|
8
|
+
# +Runtime+ exposes several methods from the top-level +Scope+ object,
|
9
|
+
# allowing runtime objects to be used as interfaces for defining
|
10
|
+
# functions, eval'ing code and running source files.
|
11
|
+
#
|
4
12
|
class Runtime
|
5
13
|
|
6
|
-
%w[ data/expression data/identifier data/
|
7
|
-
callable/function callable/macro
|
8
|
-
|
9
|
-
|
14
|
+
%w[ data/expression data/identifier data/cons
|
15
|
+
callable/function callable/syntax callable/macro callable/continuation
|
16
|
+
frame stack stackless
|
17
|
+
scope binding
|
10
18
|
|
11
19
|
].each do |file|
|
12
20
|
require RUNTIME_PATH + file
|
13
21
|
end
|
14
22
|
|
15
23
|
extend Forwardable
|
16
|
-
def_delegators(:@top_level, :[], :eval, :define, :syntax, :
|
24
|
+
def_delegators(:@top_level, :[], :eval, :exec, :define, :syntax, :run)
|
17
25
|
|
18
|
-
attr_reader :order
|
19
26
|
attr_accessor :stack, :top_level
|
20
27
|
|
28
|
+
# A +Runtime+ is initialized using a set of options. The available
|
29
|
+
# options include the following, all of which are +false+ unless
|
30
|
+
# you override them yourself:
|
31
|
+
#
|
32
|
+
# * <tt>:continuations</tt>: set to +true+ to enable <tt>call/cc</tt>
|
33
|
+
# * <tt>:lazy</tt>: set to +true+ to enable lazy evaluation
|
34
|
+
# * <tt>:unhygienic</tt>: set to +true+ to disable macro hygiene
|
35
|
+
#
|
21
36
|
def initialize(options = {})
|
22
37
|
@lazy = !!options[:lazy]
|
23
38
|
@continuations = !!options[:continuations]
|
24
39
|
@hygienic = !options[:unhygienic]
|
25
40
|
|
26
41
|
@top_level = Scope.new(self)
|
27
|
-
@stack =
|
28
|
-
|
29
|
-
syntax_type = (lazy? or not @hygienic) ? 'rb' : 'scm'
|
42
|
+
@stack = stackless? ? Stackless.new : Stack.new
|
30
43
|
|
31
44
|
run("#{ BUILTIN_PATH }primitives.rb")
|
32
|
-
run("#{ BUILTIN_PATH }syntax
|
45
|
+
run("#{ BUILTIN_PATH }syntax.scm")
|
33
46
|
run("#{ BUILTIN_PATH }library.scm")
|
34
47
|
|
35
48
|
@start_time = Time.now.to_f
|
36
49
|
end
|
37
50
|
|
38
|
-
|
39
|
-
|
40
|
-
@top_level.run(path)
|
41
|
-
end
|
42
|
-
|
51
|
+
# Returns the length of time the +Runtime+ has been alive for, as a
|
52
|
+
# number in microseconds.
|
43
53
|
def elapsed_time
|
44
54
|
(Time.now.to_f - @start_time) * 1000000
|
45
55
|
end
|
46
56
|
|
57
|
+
# Returns +true+ iff the +Runtime+ is using lazy evaluation.
|
47
58
|
def lazy?; @lazy; end
|
48
59
|
|
60
|
+
# Returns +true+ iff the +Runtime+ is using hygienic macros.
|
49
61
|
def hygienic?; @hygienic; end
|
50
62
|
|
63
|
+
# Returns +true+ iff the +Runtime+ is using the faster +Stackless+
|
64
|
+
# evaluator, which does not support <tt>(call/cc)</tt>.
|
51
65
|
def stackless?
|
52
66
|
lazy? or not @continuations
|
53
67
|
end
|
54
68
|
|
55
|
-
def
|
56
|
-
stackless? ?
|
69
|
+
def to_s
|
70
|
+
"#<runtime: #{ stackless? ? 'call/cc disabled' : 'call/cc enabled'
|
71
|
+
}, #{ hygienic? ? 'hygienic' : 'unhygienic'
|
72
|
+
}, #{ lazy? ? 'lazy' : 'eager' }>"
|
73
|
+
end
|
74
|
+
alias :inspect :to_s
|
75
|
+
|
76
|
+
def info
|
77
|
+
[ "Heist Scheme interpreter v. #{ VERSION }",
|
78
|
+
"Evaluation mode: #{ lazy? ? 'LAZY' : 'EAGER' }",
|
79
|
+
"Continuations enabled? #{ stackless? ? 'NO' : 'YES' }",
|
80
|
+
"Macros: #{ hygienic? ? 'HYGIENIC' : 'UNHYGIENIC' }\n\n"
|
81
|
+
] * "\n"
|
57
82
|
end
|
58
83
|
|
59
84
|
end
|
data/lib/runtime/scope.rb
CHANGED
@@ -1,9 +1,24 @@
|
|
1
1
|
module Heist
|
2
2
|
class Runtime
|
3
3
|
|
4
|
+
# +Scope+ is primarily used to represent symbol tables, though it also
|
5
|
+
# has a few other scope-related responsibilities such as defining
|
6
|
+
# functions (functions need to remember the scope they appear in) and
|
7
|
+
# loading files. Scheme uses lexical scope, which we model using a simple
|
8
|
+
# delegation system.
|
9
|
+
#
|
10
|
+
# Every +Scope+ has a hash (<tt>@symbols</tt>) in which it stores names
|
11
|
+
# of variables and their associated values, and a parent scope
|
12
|
+
# (<tt>@parent</tt>). If a variable cannot be found in one scope, the
|
13
|
+
# lookup is delegated to the parent until we get to the top level, at
|
14
|
+
# which point an exception is raised.
|
15
|
+
#
|
4
16
|
class Scope
|
5
17
|
attr_reader :runtime
|
6
18
|
|
19
|
+
# A +Scope+ is initialized using another +Scope+ to use as the parent.
|
20
|
+
# The parent may also be a +Runtime+ instance, indicating that the
|
21
|
+
# new +Scope+ is being used as the top level of a runtime environment.
|
7
22
|
def initialize(parent = {})
|
8
23
|
@symbols = {}
|
9
24
|
is_runtime = (Runtime === parent)
|
@@ -11,48 +26,127 @@ module Heist
|
|
11
26
|
@runtime = is_runtime ? parent : parent.runtime
|
12
27
|
end
|
13
28
|
|
29
|
+
# Returns the value corresponding to the given variable name. If the
|
30
|
+
# name does not exist in the receiver, the call is delegated to its
|
31
|
+
# parent scope. If the name cannot be found in any scope an exception
|
32
|
+
# is raised.
|
33
|
+
#
|
34
|
+
# In lazy mode, +Binding+ objects are stored in the symbol table when
|
35
|
+
# functions are called; we do not evaluate the arguments to a function
|
36
|
+
# before calling it, but instead we force an argument's value if the
|
37
|
+
# function's body attempts to access it by name.
|
38
|
+
#
|
14
39
|
def [](name)
|
15
40
|
name = to_name(name)
|
16
|
-
bound = @symbols.has_key?(
|
41
|
+
bound = @symbols.has_key?(name)
|
17
42
|
|
18
43
|
raise UndefinedVariable.new(
|
19
44
|
"Variable '#{name}' is not defined") unless bound or Scope === @parent
|
20
45
|
|
21
46
|
value = bound ? @symbols[name] : @parent[name]
|
22
|
-
value = value.
|
47
|
+
value = value.force! if value.respond_to?(:force!)
|
23
48
|
value
|
24
49
|
end
|
25
50
|
|
51
|
+
# Binds the given +value+ to the given +name+ in the receiving +Scope+.
|
52
|
+
# Note this always sets the variable in the receiver; see <tt>set!</tt>
|
53
|
+
# for a method corresponding to Scheme's <tt>(set!)</tt> function.
|
26
54
|
def []=(name, value)
|
27
55
|
@symbols[to_name(name)] = value
|
28
56
|
value.name = name if Function === value
|
29
57
|
value
|
30
58
|
end
|
31
59
|
|
60
|
+
# Returns +true+ iff the given name is bound as a variable in the
|
61
|
+
# receiving scope or in any of its ancestor scopes.
|
32
62
|
def defined?(name)
|
33
63
|
@symbols.has_key?(to_name(name)) or
|
34
64
|
(Scope === @parent and @parent.defined?(name))
|
35
65
|
end
|
36
66
|
|
37
|
-
|
67
|
+
# Returns a +Scope+ object representing the innermost scope in which
|
68
|
+
# the given name is bound. This is used to find out whether two or
|
69
|
+
# more identifiers have the same binding.
|
70
|
+
#
|
71
|
+
# outer = Scope.new
|
72
|
+
# outer['foo'] = "a value"
|
73
|
+
#
|
74
|
+
# inner = Scope.new(outer)
|
75
|
+
# inner['bar'] = "something"
|
76
|
+
#
|
77
|
+
# inner.innermost_binding('foo') #=> outer
|
78
|
+
# inner.innermost_binding('bar') #=> inner
|
79
|
+
#
|
80
|
+
def innermost_binding(name)
|
38
81
|
name = to_name(name)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
82
|
+
@symbols.has_key?(name) ?
|
83
|
+
self :
|
84
|
+
Scope === @parent ?
|
85
|
+
@parent.innermost_binding(name) :
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# Analogous to Scheme's <tt>(set!)</tt> procedure. Assigns the given
|
90
|
+
# +value+ to the given variable +name+ in the innermost region in
|
91
|
+
# which +name+ is bound. If the +name+ does not exist in the receiving
|
92
|
+
# scope, the assignment is delegated to the parent. If no visible
|
93
|
+
# binding exists for the given +name+ an exception is raised.
|
94
|
+
def set!(name, value)
|
95
|
+
scope = innermost_binding(name)
|
96
|
+
raise UndefinedVariable.new("Cannot set undefined variable '#{name}'") if scope.nil?
|
97
|
+
scope[name] = value
|
46
98
|
end
|
47
|
-
|
99
|
+
|
100
|
+
# +define+ is used to define functions using either Scheme or Ruby
|
101
|
+
# code. Takes either a name and a Ruby block to represent the function,
|
102
|
+
# or a name, a list of formal arguments and a list of body expressions.
|
103
|
+
# The <tt>(define)</tt> primitive exposes this method to the Scheme
|
104
|
+
# environment. This method allows easy extension using Ruby, for
|
105
|
+
# example:
|
106
|
+
#
|
107
|
+
# scope.define('+') |*args|
|
108
|
+
# args.inject { |a,b| a + b }
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# See +Function+ for more information.
|
112
|
+
#
|
48
113
|
def define(name, *args, &block)
|
49
114
|
self[name] = Function.new(self, *args, &block)
|
50
115
|
end
|
51
116
|
|
52
|
-
|
53
|
-
|
117
|
+
# +syntax+ is similar to +define+, but is used for defining syntactic
|
118
|
+
# forms. Heist's parser has no predefined syntax apart from generic
|
119
|
+
# Lisp paren syntax and Scheme data literals. All special forms are
|
120
|
+
# defined as special functions and stored in the symbol table, making
|
121
|
+
# them first-class objects that can be easily aliased and overridden.
|
122
|
+
#
|
123
|
+
# This method takes a name and a Ruby block. The block will be called
|
124
|
+
# with the calling +Scope+ object and a +Cons+ containing the section
|
125
|
+
# of the parse tree representing the parameters the form has been called
|
126
|
+
# with.
|
127
|
+
#
|
128
|
+
# It is not recommended that you write your own syntax using Ruby
|
129
|
+
# since it requires too much knowledge of the plumbing for features
|
130
|
+
# like tail calls and continuations. If you define new syntax using
|
131
|
+
# Scheme macros you get correct behaviour of these features for free.
|
132
|
+
#
|
133
|
+
# See +Syntax+ for more information.
|
134
|
+
#
|
135
|
+
def syntax(name, &block)
|
136
|
+
self[name] = Syntax.new(self, &block)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Parses and executes the given string of source code in the receiving
|
140
|
+
# +Scope+. Accepts strings of Scheme source and arrays of Ruby data to
|
141
|
+
# be interpreted as Scheme lists.
|
142
|
+
def eval(source)
|
143
|
+
source = Heist.parse(source)
|
144
|
+
source.eval(self)
|
54
145
|
end
|
146
|
+
alias :exec :eval
|
55
147
|
|
148
|
+
# Returns all the variable names visible in the receiving +Scope+ that
|
149
|
+
# match the given regex +pattern+. Used by the REPL for tab completion.
|
56
150
|
def grep(pattern)
|
57
151
|
base = (Scope === @parent) ? @parent.grep(pattern) : []
|
58
152
|
@symbols.each do |key, value|
|
@@ -61,25 +155,23 @@ module Heist
|
|
61
155
|
base.uniq
|
62
156
|
end
|
63
157
|
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
scope = FileScope.new(self, path)
|
75
|
-
source.eval(scope)
|
76
|
-
end
|
77
|
-
|
78
|
-
def eval(source)
|
79
|
-
source = Heist.parse(source) if String === source
|
80
|
-
source.eval(self)
|
158
|
+
# Runs the given Scheme or Ruby definition file in the receiving
|
159
|
+
# +Scope+. Note that local vars in this method can cause block vars
|
160
|
+
# to become delocalized when running Ruby files under 1.8, so make
|
161
|
+
# sure we use 'obscure' names here.
|
162
|
+
def run(_path)
|
163
|
+
return instance_eval(File.read(_path)) if File.extname(_path) == '.rb'
|
164
|
+
_path = _path + FILE_EXT unless File.file?(_path)
|
165
|
+
_source = Heist.parse(File.read(_path))
|
166
|
+
_scope = FileScope.new(self, _path)
|
167
|
+
_source.eval(_scope)
|
81
168
|
end
|
82
169
|
|
170
|
+
# Loads the given Scheme file and executes it in the global scope.
|
171
|
+
# Paths are treated as relative to the current file. If no local file
|
172
|
+
# is found, the path is assumed to refer to a module from the Heist
|
173
|
+
# standard library. The <tt>(load)</tt> primitive is a wrapper
|
174
|
+
# around this method.
|
83
175
|
def load(path)
|
84
176
|
dir = load_path.find do |dir|
|
85
177
|
File.file?("#{dir}/#{path}") or File.file?("#{dir}/#{path}#{FILE_EXT}")
|
@@ -89,16 +181,34 @@ module Heist
|
|
89
181
|
true
|
90
182
|
end
|
91
183
|
|
184
|
+
# Returns the path of the current file. The receiving scope must have
|
185
|
+
# a +FileScope+ as an ancestor, otherwise this method will return +nil+.
|
92
186
|
def current_file
|
93
187
|
@path || @parent.current_file rescue nil
|
94
188
|
end
|
95
189
|
|
96
190
|
private
|
97
191
|
|
192
|
+
# Calls the named primitive function with the given arguments, and
|
193
|
+
# returns the result of the call.
|
194
|
+
#
|
195
|
+
# TODO: this is currently hampered by the fact that Functions expect to
|
196
|
+
# be called with a +Scope+, but Ruby primitives are not given the
|
197
|
+
# current +scope+. Figure out something better.
|
198
|
+
def call(name, *params)
|
199
|
+
self[name].body.call(*params)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Converts any Ruby object to a name string. All names are downcased
|
203
|
+
# as this Scheme is case-insensitive.
|
98
204
|
def to_name(name)
|
99
205
|
name.to_s.downcase
|
100
206
|
end
|
101
207
|
|
208
|
+
# Returns the current set of directories in which to look for Scheme
|
209
|
+
# files to load. Includes the standard library path by default, and
|
210
|
+
# the directory of the current file if the receiving +Scope+ has a
|
211
|
+
# +FileScope+ as an ancestor.
|
102
212
|
def load_path
|
103
213
|
paths, file = [], current_file
|
104
214
|
paths << File.dirname(file) if file
|
@@ -106,6 +216,11 @@ module Heist
|
|
106
216
|
end
|
107
217
|
end
|
108
218
|
|
219
|
+
# A +FileScope+ is a special kind of +Scope+ used to represent the region
|
220
|
+
# of a single file. It provides Scheme code with an awareness of its
|
221
|
+
# path so it can load local files. +FileScope+ instances delegate all
|
222
|
+
# variable assignments to their parent +Scope+ (this is typically the
|
223
|
+
# global scope) so that variables are visible across files.
|
109
224
|
class FileScope < Scope
|
110
225
|
extend Forwardable
|
111
226
|
def_delegators(:@parent, :[]=)
|
data/lib/runtime/stack.rb
CHANGED
@@ -1,29 +1,116 @@
|
|
1
1
|
module Heist
|
2
2
|
class Runtime
|
3
3
|
|
4
|
+
# +Stack+ is responsible for executing code by successively evaluating
|
5
|
+
# expressions. It provides fine-grained intermediate result inspection
|
6
|
+
# to support the Scheme notion of continuations, working with the +Frame+
|
7
|
+
# and +Body+ classes to evaluate expressions and function bodies piece
|
8
|
+
# by piece. Using the +Stack+ engine allows the creation of +Continuation+
|
9
|
+
# functions, which save the current state of the stack (i.e. the state
|
10
|
+
# of any unfinished expressions and function bodies) and allow it to be
|
11
|
+
# resumed at some later time.
|
12
|
+
#
|
13
|
+
# +Stack+ inherits from +Array+, and is a last-in-first-out structure:
|
14
|
+
# the next expression evaluated is always the last expression on the
|
15
|
+
# stack.
|
16
|
+
#
|
17
|
+
# You should think of the +Stack+ as an array of +Frame+ objects that
|
18
|
+
# hold expressions and track their progress. For example, take the
|
19
|
+
# expression:
|
20
|
+
#
|
21
|
+
# (+ (- (* 8 9) (/ 21 7)) 4)
|
22
|
+
#
|
23
|
+
# Evaluating it involves evaluating each subexpression to fill in holes
|
24
|
+
# where we expect values; when all the holes in an expression have been
|
25
|
+
# filled, we can apply the resulting function to the arguments and get
|
26
|
+
# a value. Evaluating this expression causes the stack to evolve as
|
27
|
+
# follows, where STATE lists the expressions on the stack and <tt>[]</tt>
|
28
|
+
# represents a hole that is waiting for a value:
|
29
|
+
#
|
30
|
+
# PUSH: (+ (- (* 8 9) (/ 21 7)) 4)
|
31
|
+
# STATE: ([] [] 4)
|
32
|
+
#
|
33
|
+
# PUSH: +
|
34
|
+
# VALUE: #<procedure:+>
|
35
|
+
# STATE: (#<procedure:+> [] 4)
|
36
|
+
#
|
37
|
+
# PUSH: (- (* 8 9) (/ 21 7))
|
38
|
+
# STATE: (#<procedure:+> [] 4), ([] [] [])
|
39
|
+
#
|
40
|
+
# PUSH: -
|
41
|
+
# VALUE: #<procedure:->
|
42
|
+
# STATE: (#<procedure:+> [] 4), (#<procedure:-> [] [])
|
43
|
+
#
|
44
|
+
# PUSH: (* 8 9)
|
45
|
+
# STATE: (#<procedure:+> [] 4), (#<procedure:-> [] []), ([] 8 9)
|
46
|
+
#
|
47
|
+
# PUSH: *
|
48
|
+
# VALUE: #<procedure:*>
|
49
|
+
# STATE: (#<procedure:+> [] 4), (#<procedure:-> [] []), (#<procedure:*> 8 9)
|
50
|
+
#
|
51
|
+
# VALUE: 72
|
52
|
+
# STATE: (#<procedure:+> [] 4), (#<procedure:-> 72 [])
|
53
|
+
#
|
54
|
+
# PUSH: (/ 21 7)
|
55
|
+
# STATE: (#<procedure:+> [] 4), (#<procedure:-> 72 []), ([] 21 7)
|
56
|
+
#
|
57
|
+
# PUSH: /
|
58
|
+
# VALUE: #<procedure:/>
|
59
|
+
# STATE: (#<procedure:+> [] 4), (#<procedure:-> 72 []), (#<procedure:/> 21 7)
|
60
|
+
#
|
61
|
+
# VALUE: 3
|
62
|
+
# STATE: (#<procedure:+> [] 4), (#<procedure:-> 72 3)
|
63
|
+
#
|
64
|
+
# VALUE: 69
|
65
|
+
# STATE: (#<procedure:+> 69 4)
|
66
|
+
#
|
67
|
+
# VALUE: 73
|
68
|
+
#
|
69
|
+
# So we find that <tt>(+ (- (* 8 9) (/ 21 7)) 4)</tt> gives the value 73.
|
70
|
+
# Whenever a value is returned by a subexpression we must inspect it to
|
71
|
+
# see if a +Continuation+ has been called. All this inspection of
|
72
|
+
# intermediate values takes time; if you don't need full +Continuation+
|
73
|
+
# support, use the faster +Stackless+ engine instead.
|
74
|
+
#
|
4
75
|
class Stack < Array
|
5
76
|
attr_reader :value
|
6
77
|
|
78
|
+
# Pushes a new +Frame+ or +Body+ onto the +Stack+ and then executes
|
79
|
+
# the resulting code until the pushed frame returns a value, which
|
80
|
+
# is then returned.
|
7
81
|
def <<(frame)
|
8
82
|
super
|
9
83
|
clear!(size - 1)
|
10
84
|
end
|
11
85
|
|
86
|
+
# Creates and returns a copy of the stack, which represents the current
|
87
|
+
# computational state: any unfinished expressions and function bodies
|
88
|
+
# are stored in the stack. Pass +false+ to discard the final frame,
|
89
|
+
# which will typically be a call to <tt>(call/cc)</tt> when creating
|
90
|
+
# a +Continuation+.
|
12
91
|
def copy(keep_last = true)
|
13
92
|
copy = self.class.new
|
14
93
|
range = keep_last ? 0..-1 : 0...-1
|
15
94
|
self[range].each do |frame|
|
16
|
-
copy[copy.size] = frame.
|
95
|
+
copy[copy.size] = frame.clone
|
17
96
|
end
|
18
97
|
copy
|
19
98
|
end
|
20
99
|
|
100
|
+
# Fills a hole in the final +Frame+ on the +Stack+ by replacing the
|
101
|
+
# given epxression +subexpr+ with the given +value+. If the +value+
|
102
|
+
# is a +Frame+, this frame is pushed onto the stack rather than filling
|
103
|
+
# a hole in the previous frame.
|
21
104
|
def fill!(subexpr, value)
|
22
105
|
return self[size] = value if Frame === value
|
23
106
|
return @value = value if empty?
|
24
107
|
last.fill!(subexpr, value)
|
25
108
|
end
|
26
109
|
|
110
|
+
# Causes the stack to evaluate expressions in order to pop them off the
|
111
|
+
# stack, until it gets down to the size given by +limit+. The resulting
|
112
|
+
# value if returned after all necessary computations have been done,
|
113
|
+
# and if an error takes place at any point we empty the stack.
|
27
114
|
def clear!(limit = 0)
|
28
115
|
process! while size > limit
|
29
116
|
@value
|
@@ -32,6 +119,14 @@ module Heist
|
|
32
119
|
raise ex
|
33
120
|
end
|
34
121
|
|
122
|
+
# Sets the +value+ on the +Stack+, which is always the value returned by
|
123
|
+
# the last completed expression or function body. If the given +value+
|
124
|
+
# is another +Stack+, this new stack replaces the state of the receiver;
|
125
|
+
# this takes place when a +Continuation+ is called. If the +value+ is
|
126
|
+
# a +Frame+, it is pushed onto the stack and we set a flag to indicate
|
127
|
+
# that a tail call is in effect and the replacement target of the call
|
128
|
+
# needs to be repointed: the expression that generated the tail call will
|
129
|
+
# have been removed from the stack by the time the call returns.
|
35
130
|
def value=(value)
|
36
131
|
@value = value
|
37
132
|
@unwind = (Stack === @value)
|
@@ -41,6 +136,11 @@ module Heist
|
|
41
136
|
|
42
137
|
private
|
43
138
|
|
139
|
+
# Processes one piece of the final +Frame+ on the +Stack+ and inspects the
|
140
|
+
# return value. The value must be inspected to see if a +Continuation+ has
|
141
|
+
# been called (indicated by <tt>@unwind</tt>), or a tail call has taken
|
142
|
+
# place. Continuation calls replace the state of the stack, and tail calls
|
143
|
+
# need modifying so they fill the correct hole when they return.
|
44
144
|
def process!
|
45
145
|
self.value = last.process!
|
46
146
|
return if empty? or @unwind or not last.complete?
|
@@ -48,6 +148,8 @@ module Heist
|
|
48
148
|
fill!(pop.target, @value)
|
49
149
|
end
|
50
150
|
|
151
|
+
# Replaces the state of the receiver with the state of the argument. We
|
152
|
+
# call this when calling a +Continuation+, or when recovering from errors.
|
51
153
|
def restack!(stack = [])
|
52
154
|
pop while not empty?
|
53
155
|
stack.each_with_index { |frame, i| self[i] = frame }
|