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.
- checksums.yaml +7 -0
- data/.circleci/config.yml +30 -0
- data/.gitignore +57 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +30 -0
- data/LICENSE +21 -0
- data/README.md +2 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/test +8 -0
- data/fable.gemspec +34 -0
- data/fable.sublime-project +8 -0
- data/lib/fable.rb +49 -0
- data/lib/fable/call_stack.rb +351 -0
- data/lib/fable/choice.rb +31 -0
- data/lib/fable/choice_point.rb +65 -0
- data/lib/fable/container.rb +218 -0
- data/lib/fable/control_command.rb +156 -0
- data/lib/fable/debug_metadata.rb +13 -0
- data/lib/fable/divert.rb +100 -0
- data/lib/fable/glue.rb +7 -0
- data/lib/fable/ink_list.rb +425 -0
- data/lib/fable/list_definition.rb +44 -0
- data/lib/fable/list_definitions_origin.rb +35 -0
- data/lib/fable/native_function_call.rb +324 -0
- data/lib/fable/native_function_operations.rb +149 -0
- data/lib/fable/observer.rb +205 -0
- data/lib/fable/path.rb +186 -0
- data/lib/fable/pointer.rb +42 -0
- data/lib/fable/profiler.rb +287 -0
- data/lib/fable/push_pop_type.rb +11 -0
- data/lib/fable/runtime_object.rb +159 -0
- data/lib/fable/search_result.rb +20 -0
- data/lib/fable/serializer.rb +560 -0
- data/lib/fable/state_patch.rb +47 -0
- data/lib/fable/story.rb +1447 -0
- data/lib/fable/story_state.rb +915 -0
- data/lib/fable/tag.rb +14 -0
- data/lib/fable/value.rb +334 -0
- data/lib/fable/variable_assignment.rb +20 -0
- data/lib/fable/variable_reference.rb +38 -0
- data/lib/fable/variables_state.rb +327 -0
- data/lib/fable/version.rb +3 -0
- data/lib/fable/void.rb +4 -0
- data/zork_mode.rb +23 -0
- metadata +149 -0
data/lib/fable/tag.rb
ADDED
data/lib/fable/value.rb
ADDED
@@ -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
|