Class: Heist::Runtime::Scope
- Inherits:
-
Object
- Object
- Heist::Runtime::Scope
- Defined in:
- lib/heist/runtime/scope.rb
Overview
Scope
is primarily used to represent symbol tables, though it also has a few other scope-related responsibilities such as defining functions (functions need to remember the scope they appear in) and loading files. Scheme uses lexical scope, which we model using a simple delegation system.
Every Scope
has a hash (@symbols
) in which it stores names of variables and their associated values, and a parent scope (@parent
). If a variable cannot be found in one scope, the lookup is delegated to the parent until we get to the top level, at which point an exception is raised.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#runtime ⇒ Object
readonly
Returns the value of attribute runtime.
Instance Method Summary collapse
-
#[](name) ⇒ Object
Returns the value corresponding to the given variable name.
-
#[]=(name, value) ⇒ Object
Binds the given
value
to the givenname
in the receivingScope
. -
#current_file ⇒ Object
Returns the path of the current file.
-
#define(name, *args, &block) ⇒ Object
define
is used to define functions using either Scheme or Ruby code. -
#defined?(name) ⇒ Boolean
Returns
true
iff the given name is bound as a variable in the receiving scope or in any of its ancestor scopes. -
#each_var(&block) ⇒ Object
Calls the given
block
with the name of every variable visible in theScope
(given as aSymbol
) and its corresponding value. -
#eval(source) ⇒ Object
(also: #exec)
Parses and executes the given string of source code in the receiving
Scope
. -
#expand_path(path) ⇒ Object
Returns an absolute path to a requested library based on searching the current load path.
-
#initialize(parent = {}) ⇒ Scope
constructor
A
Scope
is initialized using anotherScope
to use as the parent. -
#innermost_binding(name) ⇒ Object
Returns a
Scope
object representing the innermost scope in which the given name is bound. -
#load(file) ⇒ Object
Loads the given Scheme file and executes it in the global scope.
-
#longest_prefix(name) ⇒ Object
Returns the longest shared prefix match for the given variable name stub, used to support autocompletion in the REPL.
-
#program(expressions) ⇒ Object
Executes an array of Scheme statements expressed as Ruby data.
-
#run(_path) ⇒ Object
Runs the given Scheme or Ruby definition file in the receiving
Scope
. -
#set!(name, value) ⇒ Object
Analogous to Scheme’s
(set!)
procedure. -
#syntax(name, &block) ⇒ Object
syntax
is similar todefine
, but is used for defining syntactic forms.
Constructor Details
#initialize(parent = {}) ⇒ Scope
A Scope
is initialized using another Scope
to use as the parent. The parent may also be a Runtime
instance, indicating that the new Scope
is being used as the top level of a runtime environment.
22 23 24 25 26 27 |
# File 'lib/heist/runtime/scope.rb', line 22 def initialize(parent = {}) @symbols = Trie.new is_runtime = (Runtime === parent) @parent = is_runtime ? {} : parent @runtime = is_runtime ? parent : parent.runtime end |
Instance Attribute Details
#runtime ⇒ Object (readonly)
Returns the value of attribute runtime.
17 18 19 |
# File 'lib/heist/runtime/scope.rb', line 17 def runtime @runtime end |
Instance Method Details
#[](name) ⇒ Object
Returns the value corresponding to the given variable name. If the name does not exist in the receiver, the call is delegated to its parent scope. If the name cannot be found in any scope an exception is raised.
In lazy mode, Binding
objects are stored in the symbol table when functions are called; we do not evaluate the arguments to a function before calling it, but instead we force an argument’s value if the function’s body attempts to access it by name.
39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/heist/runtime/scope.rb', line 39 def [](name) name = to_name(name) bound = @symbols.has_key?(name) raise UndefinedVariable.new( "Variable '#{name}' is not defined") unless bound or Scope === @parent value = bound ? @symbols[name] : @parent[name] value = value.force! if value.respond_to?(:force!) value end |
#[]=(name, value) ⇒ Object
Binds the given value
to the given name
in the receiving Scope
. Note this always sets the variable in the receiver; see set!
for a method corresponding to Scheme’s (set!)
function.
54 55 56 57 58 |
# File 'lib/heist/runtime/scope.rb', line 54 def []=(name, value) @symbols[to_name(name)] = value value.name = name if Function === value value end |
#current_file ⇒ Object
Returns the path of the current file. The receiving scope must have a FileScope
as an ancestor, otherwise this method will return nil
.
191 192 193 |
# File 'lib/heist/runtime/scope.rb', line 191 def current_file @path || @parent.current_file rescue nil end |
#define(name, *args, &block) ⇒ Object
define
is used to define functions using either Scheme or Ruby code. Takes either a name and a Ruby block to represent the function, or a name, a list of formal arguments and a list of body expressions. The (define)
primitive exposes this method to the Scheme environment. This method allows easy extension using Ruby, for example:
scope.define('+') |*args|
args.inject { |a,b| a + b }
end
See Function
for more information.
113 114 115 |
# File 'lib/heist/runtime/scope.rb', line 113 def define(name, *args, &block) self[name] = Function.new(self, *args, &block) end |
#defined?(name) ⇒ Boolean
Returns true
iff the given name is bound as a variable in the receiving scope or in any of its ancestor scopes.
62 63 64 65 |
# File 'lib/heist/runtime/scope.rb', line 62 def defined?(name) @symbols.has_key?(to_name(name)) or (Scope === @parent and @parent.defined?(name)) end |
#each_var(&block) ⇒ Object
Calls the given block
with the name of every variable visible in the Scope
(given as a Symbol
) and its corresponding value.
141 142 143 144 145 146 |
# File 'lib/heist/runtime/scope.rb', line 141 def each_var(&block) @parent.each_var(&block) if @parent.respond_to?(:each_var) @symbols.each do |key, value| block.call((key * '').to_sym, value) end end |
#eval(source) ⇒ Object Also known as: exec
Parses and executes the given string of source code in the receiving Scope
. Accepts strings of Scheme source and arrays of Ruby data to be interpreted as Scheme lists.
151 152 153 154 |
# File 'lib/heist/runtime/scope.rb', line 151 def eval(source) source = Heist.parse(source) source.eval(self) end |
#expand_path(path) ⇒ Object
Returns an absolute path to a requested library based on searching the current load path. The file extension may be omitted, suitable extensions being listed in Heist::FILE_EXTS.
198 199 200 201 202 203 204 205 206 207 |
# File 'lib/heist/runtime/scope.rb', line 198 def (path) load_path.each do |dir| test_path = File.(File.join(dir, path)) FILE_EXTS.each do |ext| full_path = test_path + ext return full_path if File.file?(full_path) end end nil end |
#innermost_binding(name) ⇒ Object
Returns a Scope
object representing the innermost scope in which the given name is bound. This is used to find out whether two or more identifiers have the same binding.
outer = Scope.new
outer['foo'] = "a value"
inner = Scope.new(outer)
inner['bar'] = "something"
inner.innermost_binding('foo') #=> outer
inner.innermost_binding('bar') #=> inner
80 81 82 83 84 85 86 87 |
# File 'lib/heist/runtime/scope.rb', line 80 def innermost_binding(name) name = to_name(name) @symbols.has_key?(name) ? self : Scope === @parent ? @parent.innermost_binding(name) : nil end |
#load(file) ⇒ Object
Loads the given Scheme file and executes it in the global scope. Paths are treated as relative to the current file. If no local file is found, the path is assumed to refer to a module from the Heist standard library. The (load)
primitive is a wrapper around this method.
184 185 186 187 |
# File 'lib/heist/runtime/scope.rb', line 184 def load(file) path = (file) runtime.run(path) if path end |
#longest_prefix(name) ⇒ Object
Returns the longest shared prefix match for the given variable name stub, used to support autocompletion in the REPL.
164 165 166 |
# File 'lib/heist/runtime/scope.rb', line 164 def longest_prefix(name) @symbols.longest_prefix(to_name(name)) end |
#program(expressions) ⇒ Object
Executes an array of Scheme statements expressed as Ruby data.
158 159 160 |
# File 'lib/heist/runtime/scope.rb', line 158 def program(expressions) expressions.map { |expr| exec(expr) }.last end |
#run(_path) ⇒ Object
Runs the given Scheme or Ruby definition file in the receiving Scope
. Note that local vars in this method can cause block vars to become delocalized when running Ruby files under 1.8, so make sure we use ‘obscure’ names here.
172 173 174 175 176 177 |
# File 'lib/heist/runtime/scope.rb', line 172 def run(_path) return instance_eval(File.read(_path)) if File.extname(_path) == '.rb' _source = Heist.parse(File.read(_path)) _scope = FileScope.new(self, _path) _source.eval(_scope) end |
#set!(name, value) ⇒ Object
Analogous to Scheme’s (set!)
procedure. Assigns the given value
to the given variable name
in the innermost region in which name
is bound. If the name
does not exist in the receiving scope, the assignment is delegated to the parent. If no visible binding exists for the given name
an exception is raised.
94 95 96 97 98 |
# File 'lib/heist/runtime/scope.rb', line 94 def set!(name, value) scope = innermost_binding(name) raise UndefinedVariable.new("Cannot set undefined variable '#{name}'") if scope.nil? scope[name] = value end |
#syntax(name, &block) ⇒ Object
syntax
is similar to define
, but is used for defining syntactic forms. Heist’s parser has no predefined syntax apart from generic Lisp paren syntax and Scheme data literals. All special forms are defined as special functions and stored in the symbol table, making them first-class objects that can be easily aliased and overridden.
This method takes a name and a Ruby block. The block will be called with the calling Scope
object and a Cons
containing the section of the parse tree representing the parameters the form has been called with.
It is not recommended that you write your own syntax using Ruby since it requires too much knowledge of the plumbing for features like tail calls and continuations. If you define new syntax using Scheme macros you get correct behaviour of these features for free.
See Syntax
for more information.
135 136 137 |
# File 'lib/heist/runtime/scope.rb', line 135 def syntax(name, &block) self[name] = Syntax.new(self, &block) end |