fable 0.5.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +30 -0
  3. data/.gitignore +57 -0
  4. data/.ruby-version +1 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +7 -0
  7. data/Gemfile.lock +30 -0
  8. data/LICENSE +21 -0
  9. data/README.md +2 -0
  10. data/Rakefile +10 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/bin/test +8 -0
  14. data/fable.gemspec +34 -0
  15. data/fable.sublime-project +8 -0
  16. data/lib/fable.rb +49 -0
  17. data/lib/fable/call_stack.rb +351 -0
  18. data/lib/fable/choice.rb +31 -0
  19. data/lib/fable/choice_point.rb +65 -0
  20. data/lib/fable/container.rb +218 -0
  21. data/lib/fable/control_command.rb +156 -0
  22. data/lib/fable/debug_metadata.rb +13 -0
  23. data/lib/fable/divert.rb +100 -0
  24. data/lib/fable/glue.rb +7 -0
  25. data/lib/fable/ink_list.rb +425 -0
  26. data/lib/fable/list_definition.rb +44 -0
  27. data/lib/fable/list_definitions_origin.rb +35 -0
  28. data/lib/fable/native_function_call.rb +324 -0
  29. data/lib/fable/native_function_operations.rb +149 -0
  30. data/lib/fable/observer.rb +205 -0
  31. data/lib/fable/path.rb +186 -0
  32. data/lib/fable/pointer.rb +42 -0
  33. data/lib/fable/profiler.rb +287 -0
  34. data/lib/fable/push_pop_type.rb +11 -0
  35. data/lib/fable/runtime_object.rb +159 -0
  36. data/lib/fable/search_result.rb +20 -0
  37. data/lib/fable/serializer.rb +560 -0
  38. data/lib/fable/state_patch.rb +47 -0
  39. data/lib/fable/story.rb +1447 -0
  40. data/lib/fable/story_state.rb +915 -0
  41. data/lib/fable/tag.rb +14 -0
  42. data/lib/fable/value.rb +334 -0
  43. data/lib/fable/variable_assignment.rb +20 -0
  44. data/lib/fable/variable_reference.rb +38 -0
  45. data/lib/fable/variables_state.rb +327 -0
  46. data/lib/fable/version.rb +3 -0
  47. data/lib/fable/void.rb +4 -0
  48. data/zork_mode.rb +23 -0
  49. metadata +149 -0
@@ -0,0 +1,14 @@
1
+ module Fable
2
+ class Tag < RuntimeObject
3
+ attr_accessor :text
4
+
5
+ def initialize(tag_text)
6
+ super()
7
+ self.text = tag_text
8
+ end
9
+
10
+ def to_s
11
+ return "# #{self.text}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,334 @@
1
+ module Fable
2
+ class Value < RuntimeObject
3
+ attr_accessor :value_object
4
+ alias_method :value, :value_object
5
+ alias_method :value=, :value_object=
6
+
7
+
8
+ def initialize(value)
9
+ super()
10
+ self.value_object = value
11
+ end
12
+
13
+ def value_type
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def truthy?
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def cast(new_type)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def to_s
26
+ value_object.to_s
27
+ end
28
+
29
+ def self.create(value)
30
+ case value
31
+ when TrueClass, FalseClass
32
+ converted_to_int = value ? 1 : 0
33
+ return IntValue.new(converted_to_int)
34
+ when Integer
35
+ return IntValue.new(value)
36
+ when Numeric
37
+ return FloatValue.new(value.to_f)
38
+ when String
39
+ return StringValue.new(value)
40
+ when Path
41
+ return DivertTargetValue.new(value)
42
+ when InkList
43
+ return ListValue.new(value)
44
+ else
45
+ return nil
46
+ end
47
+ end
48
+
49
+ def copy
50
+ return self.class.create(value_object)
51
+ end
52
+
53
+ def bad_cast_exception(target_type)
54
+ return StoryError.new("Can't cast #{self.value_object} from #{OrderedValueTypes.key(self.value_type)} to #{target_type}")
55
+ end
56
+ end
57
+
58
+ class IntValue < Value
59
+ def value_type
60
+ return OrderedValueTypes[IntValue]
61
+ end
62
+
63
+ def truthy?
64
+ return value != 0
65
+ end
66
+
67
+ def initialize(value=0)
68
+ super(value)
69
+ end
70
+
71
+ def cast(new_type)
72
+ if new_type == self.class
73
+ return self
74
+ end
75
+
76
+ if new_type == FloatValue
77
+ return FloatValue.new(self.value.to_f)
78
+ end
79
+
80
+ if new_type == StringValue
81
+ return StringValue.new(self.value.to_s)
82
+ end
83
+
84
+ raise bad_cast_exception(new_type)
85
+ end
86
+ end
87
+
88
+ class FloatValue < Value
89
+ def value_type
90
+ return FloatValue
91
+ end
92
+
93
+ def truthy?
94
+ return value != 0.0
95
+ end
96
+
97
+ def initialize(value = 0.0)
98
+ super(value)
99
+ end
100
+
101
+ def cast(new_type)
102
+ if new_type == self.class
103
+ return self
104
+ end
105
+
106
+ if new_type == IntValue
107
+ return IntValue.new(self.value.to_i)
108
+ end
109
+
110
+ if new_type == StringValue
111
+ return StringValue.new(self.value.to_s)
112
+ end
113
+
114
+ raise bad_cast_exception(new_type)
115
+ end
116
+
117
+ def to_s
118
+ if value % 1 == 0
119
+ value.to_i.to_s
120
+ else
121
+ value.round(7).to_s
122
+ end
123
+ end
124
+ end
125
+
126
+ class StringValue < Value
127
+ attr_accessor :is_newline, :is_inline_whitespace
128
+
129
+ alias_method :is_newline?, :is_newline
130
+ alias_method :is_inline_whitespace?, :is_inline_whitespace
131
+
132
+ def value_type
133
+ return OrderedValueTypes[StringValue]
134
+ end
135
+
136
+ def truthy?
137
+ return value.length > 0
138
+ end
139
+
140
+ def is_nonwhitespace?
141
+ return !is_newline? && !is_inline_whitespace?
142
+ end
143
+
144
+ def initialize(*args)
145
+ if args.size == 1
146
+ self.initialize_with_string(args[0])
147
+ else
148
+ super("")
149
+ end
150
+ end
151
+
152
+ def initialize_with_string(value)
153
+ #classify whitespace status
154
+ self.is_newline = (value == "\n")
155
+ self.is_inline_whitespace = true
156
+
157
+ value.each_char do |character|
158
+ if character != ' ' && character != "\t"
159
+ self.is_inline_whitespace = false
160
+ break
161
+ end
162
+ end
163
+
164
+ self.value = value
165
+ end
166
+
167
+ def cast(new_type)
168
+ if new_type == self.class
169
+ return self
170
+ end
171
+
172
+ if new_type == IntValue
173
+ begin
174
+ return IntValue.new(Integer(self.value))
175
+ rescue ArgumentError => e
176
+ return nil
177
+ end
178
+ end
179
+
180
+ if new_type == FloatValue
181
+ begin
182
+ return FloatValue.new(Float(self.value))
183
+ rescue ArgumentError => e
184
+ return nil
185
+ end
186
+ end
187
+
188
+ raise bad_cast_exception(new_type)
189
+ end
190
+ end
191
+
192
+ class DivertTargetValue < Value
193
+ alias_method :target_path, :value
194
+ alias_method :target_path=, :value=
195
+
196
+ def value_type
197
+ return OrderedValueTypes[DivertTargetValue]
198
+ end
199
+
200
+ def truthy?
201
+ raise Error, "Shouldn't be checking the truthiness of a divert target"
202
+ end
203
+
204
+ def initialize(value=nil)
205
+ super(value)
206
+ end
207
+
208
+ def cast(new_type)
209
+ if new_type == self.class
210
+ return self
211
+ end
212
+
213
+ raise bad_cast_exception(new_type)
214
+ end
215
+
216
+ def to_s
217
+ return "DivertTargetValue(#{target_path})"
218
+ end
219
+ end
220
+
221
+ class VariablePointerValue < Value
222
+ # Where the variable is located
223
+ # -1 = default, unknown, to be determined
224
+ # 0 = in global scope
225
+ # 1+ = callstack element index + 1 (so that the first doesn't conflict with special global scope)
226
+ attr_accessor :context_index
227
+
228
+ alias_method :variable_name, :value
229
+ alias_method :variable_name=, :value=
230
+
231
+ def value_type
232
+ return OrderedValueTypes[VariablePointerValue]
233
+ end
234
+
235
+ def truthy?
236
+ raise Error, "Shouldn't be checking the truthiness of a variable pointer"
237
+ end
238
+
239
+ def initialize(variable_name=nil, context_index = -1)
240
+ super(variable_name)
241
+ self.context_index = context_index
242
+ end
243
+
244
+ def cast(new_type)
245
+ if new_type == self.class
246
+ return self
247
+ end
248
+
249
+ raise bad_cast_exception(new_type)
250
+ end
251
+
252
+ def to_s
253
+ return "VariablePointerValue(#{variable_name})"
254
+ end
255
+
256
+ def copy
257
+ return VariablePointerValue.new(variable_name, context_index)
258
+ end
259
+ end
260
+
261
+ class ListValue < Value
262
+ def value_type
263
+ return OrderedValueTypes[ListValue]
264
+ end
265
+
266
+ def truthy?
267
+ return value.count > 0
268
+ end
269
+
270
+ def cast(new_type)
271
+ if new_type == IntValue
272
+ max = value.max_item
273
+ if max.nil?
274
+ return IntValue.new(0)
275
+ else
276
+ return IntValue.new(max)
277
+ end
278
+ end
279
+
280
+ if new_type == FloatValue
281
+ max = value.max_item
282
+ if max.nil?
283
+ return FloatValue.new(0.0)
284
+ else
285
+ return FloatValue.new(max)
286
+ end
287
+ end
288
+
289
+ if new_type == StringValue
290
+ max = value.max_item
291
+ if max.nil?
292
+ return StringValue.new("")
293
+ else
294
+ return StringValue.new(max)
295
+ end
296
+ end
297
+
298
+ if new_type == self.class
299
+ return self
300
+ end
301
+
302
+ raise bad_cast_exception(new_type)
303
+ end
304
+
305
+ def initialize(single_item=nil, single_value=nil)
306
+ if single_item.nil? && single_value.nil?
307
+ super(InkList.new)
308
+ elsif single_item.is_a?(InkList)
309
+ super(InkList.copy_list(single_item))
310
+ else
311
+ super(InkList.new_with_single_item(single_item, single_value))
312
+ end
313
+ end
314
+
315
+ def self.retain_list_origins_for_assignment(old_value, new_value)
316
+ # When assigning the empty list, try to retain any initial origin names
317
+ if (old_value.is_a?(ListValue) && new_value.is_a?(ListValue) && new_value.value.list.size == 0)
318
+ new_value.value.set_initial_origin_names(old_value.value.origin_names)
319
+ end
320
+ end
321
+ end
322
+
323
+ OrderedValueTypes = {
324
+ # Used in coercion
325
+ IntValue => 0,
326
+ FloatValue => 1,
327
+ ListValue => 2,
328
+ StringValue => 3,
329
+
330
+ # Not used for coercion described above
331
+ DivertTargetValue => 4,
332
+ VariablePointerValue => 5
333
+ }.freeze
334
+ end
@@ -0,0 +1,20 @@
1
+ module Fable
2
+ # The value to be assigned is popped off the evaluation stack,
3
+ # so no need to keep it here
4
+ class VariableAssignment < RuntimeObject
5
+ attr_accessor :variable_name, :new_declaration, :global
6
+
7
+ alias_method :new_declaration?, :new_declaration
8
+ alias_method :global?, :global
9
+
10
+ def initialize(variable_name, new_declaration)
11
+ super()
12
+ self.variable_name = variable_name
13
+ self.new_declaration = new_declaration
14
+ end
15
+
16
+ def to_s
17
+ return "VarAssign to #{variable_name}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,38 @@
1
+ module Fable
2
+ class VariableReference < RuntimeObject
3
+ attr_accessor :name, :path_for_count
4
+
5
+ def container_for_count
6
+ return self.resolve_path(self.path_for_count).container
7
+ end
8
+
9
+ def path_string_for_count
10
+ if path_for_count.nil?
11
+ return nil
12
+ end
13
+
14
+ return compact_path_string(path_for_count)
15
+ end
16
+
17
+ def path_string_for_count=(value)
18
+ if value.nil?
19
+ self.path_for_count = nil
20
+ else
21
+ self.path_for_count = Path.new(value)
22
+ end
23
+ end
24
+
25
+ def initialize(name)
26
+ super()
27
+ self.name = name
28
+ end
29
+
30
+ def to_s
31
+ if !name.nil?
32
+ return "var(#{name})"
33
+ else
34
+ return "read_count(#{path_string_for_count})"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,327 @@
1
+ module Fable
2
+ # Encompasses all the global variables in an ink Story, and
3
+ # allows binding of a variable_changed event so that that game
4
+ # code can be notified whenever the global variables change.
5
+ class VariablesState
6
+ attr_accessor :patch, :batch_observing_variable_changes,
7
+ :changed_variables_for_batch_observing, :callstack,
8
+ :globals, :list_definitions_origins,
9
+ :default_global_variables,
10
+ :variable_observers
11
+
12
+ def has_variable_observers?
13
+ self.variable_observers.all?{|name, observers| !observers.empty? }
14
+ end
15
+
16
+ def add_variable_observer(variable_name, &block)
17
+ self.variable_observers[variable_name] ||= []
18
+ self.variable_observers[variable_name] << block
19
+ return true
20
+ end
21
+
22
+ def remove_variable_observer(variable_name, &block)
23
+ self.variable_observers[variable_name] ||= []
24
+ self.variable_observers[variable_name].delete(block)
25
+ return true
26
+ end
27
+
28
+ def variable_changed_event(variable_name, current_value)
29
+ if variable_observers.has_key?(variable_name)
30
+ variable_observers[variable_name].each do |block|
31
+ block.call(variable_name, current_value.value)
32
+ end
33
+ end
34
+ end
35
+
36
+ def batch_observing_variable_changes=(value)
37
+ @batch_observing_variable_changes = value
38
+ if value
39
+ @changed_variables_for_batch_observing = []
40
+
41
+ # Finished observing variables in a batch, now send
42
+ # notifications for changed variables all in one go
43
+ else
44
+ if @changed_variables_for_batch_observing != nil
45
+ @changed_variables_for_batch_observing.uniq.each do |variable_name|
46
+ current_value = @globals[variable_name]
47
+ variable_changed_event(variable_name, current_value)
48
+ end
49
+ end
50
+
51
+ @changed_variables_for_batch_observing = nil
52
+ end
53
+ end
54
+
55
+ def [](variable_name)
56
+ if !patch.nil? && patch.get_global(variable_name)
57
+ return patch.get_global(variable_name).value_object
58
+ end
59
+
60
+ # Search main dictionary first
61
+ # If it's not found, it might be because the story content has
62
+ # changed, and the original default value hasn't been instantiated
63
+ variable_value = @globals[variable_name] || @default_global_variables[variable_name]
64
+ if !variable_value.nil?
65
+ return variable_value.value_object
66
+ else
67
+ return nil
68
+ end
69
+ end
70
+
71
+ def []=(variable_name, given_value)
72
+ if !default_global_variables.has_key?(variable_name)
73
+ raise Error, "Cannot assign to a variable (#{variable_name}) that hasn't been declared in the story"
74
+ end
75
+
76
+ value = Value.create(given_value)
77
+ if value.nil?
78
+ if given_value.nil?
79
+ raise Error, "Cannot pass nil to VariableState"
80
+ else
81
+ raise Error, "Invalid value passed to VariableState: #{given_value}"
82
+ end
83
+ end
84
+
85
+ set_global(variable_name, value)
86
+ end
87
+
88
+ def initialize(callstack, list_definitions_origins)
89
+ self.globals = {}
90
+ self.callstack = callstack
91
+ self.list_definitions_origins = list_definitions_origins
92
+ self.dont_save_default_values = true
93
+ self.variable_observers = {}
94
+ end
95
+
96
+ def apply_patch!
97
+ patch.globals.each do |name, value|
98
+ self.globals[name] = value
99
+ end
100
+
101
+ if !changed_variables_for_batch_observing.nil?
102
+ patch.changed_variables.each do |name|
103
+ changed_variables_for_batch_observing << name
104
+ end
105
+ end
106
+
107
+ patch = nil
108
+ end
109
+
110
+ def from_hash!(hash_to_use)
111
+ @globals = {}
112
+ @default_global_variables.each do |key, value|
113
+ if hash_to_use.has_key?(key)
114
+ @globals[key] = Serializer.convert_to_runtime_object(hash_to_use[key])
115
+ else
116
+ @globals[key] = value
117
+ end
118
+ end
119
+ end
120
+
121
+ # When saving out state, we can skip saving global values that
122
+ # remain equal to the initial values that were declared in ink.
123
+ # This makes the save object (potentially) much smaller assuming that
124
+ # at least a portion of the globals haven't changed. However, it
125
+ # can also take marginally longer to save in the case that the
126
+ # majority HAVE changed, since it has to compare all globals.
127
+ # It may also be useful to turn this off for testing worst case
128
+ # save timing.
129
+ attr_accessor :dont_save_default_values
130
+ alias_method :dont_save_default_values?, :dont_save_default_values
131
+
132
+ def to_hash
133
+ export = {}
134
+
135
+ self.globals.each do |key, value|
136
+ if dont_save_default_values?
137
+ # Don't write out values that are the same as the default global
138
+ # values
139
+ if default_global_variables.has_key?(key)
140
+ if runtime_objects_equal?(default_global_variables[key], value)
141
+ next
142
+ end
143
+ end
144
+ end
145
+
146
+ export[key] = Serializer.convert_from_runtime_object(value)
147
+ end
148
+
149
+ return export
150
+ end
151
+
152
+ def runtime_objects_equal?(object_1, object_2)
153
+ if object_1.class != object_2.class
154
+ return false
155
+ end
156
+
157
+ # Perform equality on int/float manually to avoid boxing
158
+ if object_1.is_a?(IntValue) || object_1.is_a?(FloatValue)
159
+ return object_1.value == object_2.value
160
+ end
161
+
162
+ if object_1.is_a?(Value)
163
+ return object_1.value_object.equal?(object_2.value_object)
164
+ end
165
+
166
+ raise Error, "FastRoughDefinitelyEquals: Unsupported runtime object type: #{object_1.class}"
167
+ end
168
+
169
+ def get_variable_with_name(variable_name)
170
+ return get_variable_with_name_internal(variable_name, -1)
171
+ end
172
+
173
+ def get_default_variable_value(variable_name)
174
+ return self.default_global_variables[variable_name]
175
+ end
176
+
177
+ def global_variable_exists_with_name?(variable_name)
178
+ globals.has_key?(variable_name) || (!default_global_variables.nil? && default_global_variables.has_key?(variable_name))
179
+ end
180
+
181
+ def get_variable_with_name_internal(variable_name, context_index)
182
+ variable_value = get_raw_variable_with_name(variable_name, context_index)
183
+
184
+ # Get value from pointer?
185
+ if variable_value.is_a?(VariablePointerValue)
186
+ variable_value = get_variable_with_name_internal(variable_value.variable_name, variable_value.context_index)
187
+ end
188
+
189
+ return variable_value
190
+ end
191
+
192
+ def get_raw_variable_with_name(variable_name, context_index)
193
+ variable_value = nil
194
+ if context_index == 0 || context_index == -1
195
+ if !patch.nil? && patch.get_global(variable_name)
196
+ return patch.get_global(variable_name)
197
+ end
198
+
199
+ if globals.has_key?(variable_name)
200
+ return globals[variable_name]
201
+ end
202
+
203
+ # Getting variables can actually happen during global setup because
204
+ # you can do VAR x = A_LIST_ITEM
205
+ # so default_global_variables may be null
206
+ # WE need to do this check though in case a new global is added, so we need to
207
+ # revert to the default globals dictionary, since an initial value hasn't been set yet
208
+ if !default_global_variables.nil? && default_global_variables.has_key?(variable_name)
209
+ return default_global_variables[variable_name]
210
+ end
211
+
212
+ list_item_value = list_definitions_origins.find_single_item_list_with_name(variable_name)
213
+
214
+ if list_item_value
215
+ return list_item_value
216
+ end
217
+ end
218
+
219
+ # Temporary
220
+ return callstack.get_temporary_variable_with_name(variable_name, context_index)
221
+ end
222
+
223
+ def assign(variable_assignment, value)
224
+ name = variable_assignment.variable_name
225
+ context_index = -1
226
+
227
+ # Are we assigning to a global variable?
228
+ set_global = false
229
+ if variable_assignment.new_declaration?
230
+ set_global = variable_assignment.global?
231
+ else
232
+ set_global = global_variable_exists_with_name?(name)
233
+ end
234
+
235
+ # Constructing new variable pointer reference
236
+ if variable_assignment.new_declaration?
237
+ if value.is_a?(VariablePointerValue)
238
+ fully_resolved_variable_pointer = resolve_variable_pointer!(value)
239
+ value = fully_resolved_variable_pointer
240
+ end
241
+ # Assign to existing variable pointer?
242
+ # then assign to the variable that the pointer is pointing to by name
243
+ else
244
+ # De-reference variable reference to point to
245
+ existing_pointer = get_raw_variable_with_name(name, context_index)
246
+ while existing_pointer && existing_pointer.is_a?(VariablePointerValue)
247
+ name = existing_pointer.variable_name
248
+ context_index = existing_pointer.context_index
249
+ set_global = (context_index == 0)
250
+ existing_pointer = get_raw_variable_with_name(name, context_index)
251
+ end
252
+ end
253
+
254
+ if set_global
255
+ set_global(name, value)
256
+ else
257
+ callstack.set_temporary_variable(name, value, variable_assignment.new_declaration?, context_index)
258
+ end
259
+ end
260
+
261
+ def snapshot_default_globals
262
+ self.default_global_variables = self.globals.dup
263
+ end
264
+
265
+ def set_global(variable_name, value)
266
+ old_value = nil
267
+ if patch.nil? || !patch.get_global(variable_name)
268
+ old_value = globals[variable_name]
269
+ end
270
+
271
+ ListValue.retain_list_origins_for_assignment(old_value, value)
272
+
273
+ if !patch.nil?
274
+ patch.set_global(variable_name, value)
275
+ else
276
+ self.globals[variable_name] = value
277
+ end
278
+
279
+ if has_variable_observers? && !value.equal?(old_value)
280
+ if batch_observing_variable_changes
281
+ if !patch.nil?
282
+ patch.add_changed_variable(variable_name)
283
+ elsif !changed_variables_for_batch_observing.nil?
284
+ changed_variables_for_batch_observing << variable_name
285
+ end
286
+ else
287
+ variable_changed_event(variable_name, value)
288
+ end
289
+ end
290
+ end
291
+
292
+ # Given a variable pointer with just the name of the target known, resolve to a variable
293
+ # pointer that more specifically points to the exact instance: whether it's global,
294
+ # or the exact position of a temporary on the callstack.
295
+ def resolve_variable_pointer!(variable_pointer)
296
+ context_index = variable_pointer.context_index
297
+
298
+ if context_index == -1
299
+ context_index = get_context_index_of_variable_named(variable_pointer.variable_name)
300
+ end
301
+
302
+ value_of_variable_pointed_to = get_raw_variable_with_name(variable_pointer.variable_name, context_index)
303
+
304
+ # Extra layer of indirection:
305
+ # When accessing a pointer to a pointer (e.g. when calling nested or
306
+ # recursive functions that take a variable references, ensure we don't create
307
+ # a chain of indirection by just returning the final target.
308
+ if value_of_variable_pointed_to.is_a?(VariablePointerValue)
309
+ return value_of_variable_pointed_to
310
+ # Make a copy of the variable pointer so we're not using the value directly
311
+ # from the runtime. Temporary must bne local to the current scope
312
+ else
313
+ return VariablePointerValue.new(variable_pointer.variable_name, context_index)
314
+ end
315
+ end
316
+
317
+ # 0 if named variable is global
318
+ # =! if named variable is a temporary in a particular callstack element
319
+ def get_context_index_of_variable_named(variable_name)
320
+ if global_variable_exists_with_name?(variable_name)
321
+ return 0
322
+ end
323
+
324
+ return callstack.current_element_index
325
+ end
326
+ end
327
+ end