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
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 }
|