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
@@ -0,0 +1,13 @@
|
|
1
|
+
module Fable
|
2
|
+
class DebugMetadata
|
3
|
+
attr_accessor :start_line_number, :end_line_number, :file_name, :source_name
|
4
|
+
|
5
|
+
def to_s
|
6
|
+
if !file_name.nil?
|
7
|
+
"line #{start_line_number} of #{file_name}"
|
8
|
+
else
|
9
|
+
"line #{start_line_number}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/fable/divert.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Fable
|
2
|
+
class Divert < RuntimeObject
|
3
|
+
attr_accessor :target_path, :target_pointer,
|
4
|
+
:variable_divert_name, :pushes_to_stack, :stack_push_type, :is_external,
|
5
|
+
:external_arguments, :is_conditional
|
6
|
+
|
7
|
+
alias_method :pushes_to_stack?, :pushes_to_stack
|
8
|
+
alias_method :is_external?, :is_external
|
9
|
+
alias_method :is_conditional?, :is_conditional
|
10
|
+
|
11
|
+
def target_path
|
12
|
+
# Resolve any relative paths to global paths as we come across them
|
13
|
+
if !@target_path.nil? && @target_path.relative?
|
14
|
+
target_object = target_pointer.resolve!
|
15
|
+
if !target_pointer.nil?
|
16
|
+
@target_path = target_object.path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
return @target_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def target_path=(value)
|
24
|
+
@target_path = value
|
25
|
+
@target_pointer = Pointer.null_pointer
|
26
|
+
end
|
27
|
+
|
28
|
+
def target_pointer
|
29
|
+
if @target_pointer.null_pointer?
|
30
|
+
target_object = resolve_path(@target_path).object
|
31
|
+
if @target_path.components.last.is_index?
|
32
|
+
@target_pointer.container = target_object.parent
|
33
|
+
@target_pointer.index = @target_path.components.last.index
|
34
|
+
else
|
35
|
+
@target_pointer = Pointer.start_of(target_object)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return @target_pointer
|
40
|
+
end
|
41
|
+
|
42
|
+
def target_path_string
|
43
|
+
return nil if target_path.nil?
|
44
|
+
|
45
|
+
return compact_path_string(target_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def target_path_string=(value)
|
49
|
+
if value.nil?
|
50
|
+
self.target_path = nil
|
51
|
+
else
|
52
|
+
self.target_path = Path.new(value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def ==(other_divert)
|
57
|
+
if other_divert.is_a?(Divert) && !other_divert.nil?
|
58
|
+
if self.has_variable_target? == other_divert.has_variable_target?
|
59
|
+
if self.has_variable_target?
|
60
|
+
return self.variable_divert_name == other_divert.variable_divert_name
|
61
|
+
else
|
62
|
+
return self.target_path == other_divert.target_path
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
|
69
|
+
def has_variable_target?
|
70
|
+
!variable_divert_name.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s
|
74
|
+
if has_variable_target?
|
75
|
+
return "Divert(variable: #{variable_divert_name})"
|
76
|
+
elsif target_path.nil?
|
77
|
+
return "Divert(null)"
|
78
|
+
else
|
79
|
+
result = ""
|
80
|
+
|
81
|
+
target_string = target_path.to_s
|
82
|
+
target_line_number = debug_line_number_of_path(target_path)
|
83
|
+
if !target_line_number.nil?
|
84
|
+
target_string = "line #{target_line_number}"
|
85
|
+
end
|
86
|
+
|
87
|
+
push_type = ""
|
88
|
+
if pushes_to_stack?
|
89
|
+
if stack_push_type == :FUNCTION
|
90
|
+
push_type = " function"
|
91
|
+
else
|
92
|
+
push_type = " tunnel"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
"Divert#{'?' if is_conditional?}#{push_type} -> #{target_path_string} (#{target_string})"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/fable/glue.rb
ADDED
@@ -0,0 +1,425 @@
|
|
1
|
+
module Fable
|
2
|
+
# The InkList is the underlying type that's used to store an instance of a
|
3
|
+
# list in ink. It's not used for the *definition* of the list, but for a list
|
4
|
+
# value that's stored in a variable.
|
5
|
+
class InkList
|
6
|
+
# The underlying type for a list item in ink. It stores the original list definition
|
7
|
+
# name as well as the item name, but without the value of the item. When the value is
|
8
|
+
# stored, it's stored in a Dictionary of InkListItem and an integer.
|
9
|
+
class InkListItem
|
10
|
+
#The name of the list where the item was originally defined.
|
11
|
+
attr_accessor :origin_name
|
12
|
+
|
13
|
+
# The main name of the item as defined in ink
|
14
|
+
attr_accessor :item_name
|
15
|
+
|
16
|
+
def initialize(options)
|
17
|
+
# Create an item from a dot-separated string of the form
|
18
|
+
# `list_definition_name.list_item_name`
|
19
|
+
if options.has_key?(:full_name)
|
20
|
+
name_parts = options[:full_name].split(".")
|
21
|
+
self.origin_name = name_parts[0]
|
22
|
+
self.item_name = name_parts[1]
|
23
|
+
else
|
24
|
+
self.origin_name = options[:origin_name]
|
25
|
+
self.item_name = options[:item_name]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.Null
|
30
|
+
return self.new(origin_name: nil, item_name: nil)
|
31
|
+
end
|
32
|
+
|
33
|
+
def null_item?
|
34
|
+
origin_name.nil? && item_name.null?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get the full dot-separated name of the item, in the form of
|
38
|
+
# `list_definition_name.list_item_name`
|
39
|
+
def full_name
|
40
|
+
return "#{origin_name.nil? ? "?" : origin_name}.#{item_name}"
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :to_s, :full_name
|
44
|
+
|
45
|
+
def equal?(other_object)
|
46
|
+
return false if !other_object.is_a?(InkListItem)
|
47
|
+
|
48
|
+
return (
|
49
|
+
other_object.item_name == self.item_name &&
|
50
|
+
other_object.origin_name == self.origin_name
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
attr_accessor :list, :origins
|
57
|
+
|
58
|
+
# Create a new empty ink list
|
59
|
+
def initialize
|
60
|
+
self.list = {}
|
61
|
+
self.origins = []
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.copy_list(other_list)
|
65
|
+
ink_list = self.new
|
66
|
+
other_list_data = other_list.list.map{|item, int_value| [InkList::InkListItem.new(full_name: item.full_name), int_value] }
|
67
|
+
ink_list.list = Hash[other_list_data]
|
68
|
+
ink_list.origins = other_list.origins
|
69
|
+
ink_list.set_initial_origin_names(other_list.origin_names)
|
70
|
+
|
71
|
+
return ink_list
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.new_with_single_item(item, value)
|
75
|
+
ink_list = self.new
|
76
|
+
ink_list.list[item] = value
|
77
|
+
|
78
|
+
return ink_list
|
79
|
+
end
|
80
|
+
|
81
|
+
# Create a new empty ink list that's intended to hold items from a
|
82
|
+
# particular origin list definition. The origin story is needed in order
|
83
|
+
# to be able to look up that definition
|
84
|
+
def self.new_for_origin_definition_and_story(single_origin_list_name, origin_story)
|
85
|
+
ink_list = self.new
|
86
|
+
ink_list.set_initial_origin_name(single_origin_list_name)
|
87
|
+
|
88
|
+
list_definition = origin_story.list_definitions.find_list(single_origin_list_name)
|
89
|
+
if list_definition.nil?
|
90
|
+
raise Error("InkList origin could not be found in story when constructing new list: #{single_origin_list_name}")
|
91
|
+
else
|
92
|
+
ink_list.origins = [list_definition]
|
93
|
+
end
|
94
|
+
|
95
|
+
return ink_list
|
96
|
+
end
|
97
|
+
|
98
|
+
# Converts a string to an ink list, and returns for use in the Story
|
99
|
+
def self.from_string(my_list_item, origin_story)
|
100
|
+
list_value = origin_story.list_definitions[my_list_item]
|
101
|
+
if list_value.nil?
|
102
|
+
raise Error("Could not find the InkListItem from the string '#{my_list_item}' to create an InkList because it doesn't exist in the original list definition in ink.")
|
103
|
+
else
|
104
|
+
ink_list = self.new
|
105
|
+
ink_list.list = list_value
|
106
|
+
end
|
107
|
+
|
108
|
+
return ink_list
|
109
|
+
end
|
110
|
+
|
111
|
+
def item_with_value(value)
|
112
|
+
list.key(value)
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_item(item_or_item_name)
|
116
|
+
if item_or_item_name.is_a?(InkListItem)
|
117
|
+
add_ink_list_item(item_or_item_name)
|
118
|
+
else
|
119
|
+
add_item_from_string(item_or_item_name)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Adds the given item to the ink list. Note that the item must come from a list
|
124
|
+
# definition that is already "known" to this list, so that teh item's value can be
|
125
|
+
# looked up. By "known", we mean that it alreadyu has items in it from that source,
|
126
|
+
# or did at one point. It can't be a completely fresh empty list, or a list that only
|
127
|
+
# contains items from a different list definition
|
128
|
+
def add_ink_list_item(item)
|
129
|
+
if item.origin_name.nil?
|
130
|
+
return add_item_from_string(item.item_name)
|
131
|
+
end
|
132
|
+
|
133
|
+
origins.each do |origin|
|
134
|
+
if origin.name == item.origin_name
|
135
|
+
integer_value = origin.items[item]
|
136
|
+
|
137
|
+
if integer_value.nil?
|
138
|
+
raise Error("Could not add the item '#{item.item_name}' to this list because it doesn't exist in the original list definition in ink.")
|
139
|
+
else
|
140
|
+
self.list[item] = integer_value
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
raise Error("Failed to add item to list because the item was from a new list definition that wasn't previously known to this list. Only items from previously known lists can be used, so that the int value can be found.")
|
146
|
+
end
|
147
|
+
|
148
|
+
# Adds the given item to the ink list, attempting to find the origin list definition
|
149
|
+
# that it belongs to. The item must therefore come from a list definition that is already
|
150
|
+
# "known" to this list, so that the item's value can be looked up. By "known", we mean that it
|
151
|
+
# already has items in it from that source, or it did at one point. It can't be a completely
|
152
|
+
# fresh empty list, or a list that only contains items from a different list definition
|
153
|
+
def add_item_from_string(item_name)
|
154
|
+
found_list_definition = nil
|
155
|
+
origins.each do |origin|
|
156
|
+
if origin.items.any?{|item, int_value| item.name == item_name }
|
157
|
+
if found_list_definition.nil?
|
158
|
+
found_list_definition = origin
|
159
|
+
else
|
160
|
+
raise Error("Could not add the item '#{item_name}' to this list because it could come from either '#{origin.name}' or '#{found_list_definition.name}'")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
if found_list_definition.nil?
|
166
|
+
raise Error("Could not add the item '#{item_name}' to this list because it isn't known to any list definitions previously associated with this list.")
|
167
|
+
end
|
168
|
+
|
169
|
+
item = InkListItem.new(origin_name: found_list_definition.name, item_name: item_name)
|
170
|
+
item_value = found_list_definition.value_for_item(item)
|
171
|
+
self.items[item] = item_value
|
172
|
+
end
|
173
|
+
|
174
|
+
def include_item_named?(item_name)
|
175
|
+
list.any?{|item, int_value| item.name == item_name}
|
176
|
+
end
|
177
|
+
|
178
|
+
# Story has to set this so that the value knows its origin, necessary for
|
179
|
+
# certain operations (eg: iteracting with ints). Only the story has access
|
180
|
+
# to the full set of lists, so that the origin can be resolved from the
|
181
|
+
# origin_list_name
|
182
|
+
def origin_of_max_item
|
183
|
+
return nil if origins.nil?
|
184
|
+
max_origin_name = max_item.origin_name
|
185
|
+
origins.find{|origin| origin.name == max_origin_name }
|
186
|
+
end
|
187
|
+
|
188
|
+
# Origin name needs to be serialized when content is empty, assuming
|
189
|
+
# a name is available, for list definitions with variable that is currently
|
190
|
+
# empty
|
191
|
+
def origin_names
|
192
|
+
if self.list.any?
|
193
|
+
@origin_names = self.list.map{|item, int_value| item.origin_name }.compact.uniq
|
194
|
+
end
|
195
|
+
|
196
|
+
return @origin_names
|
197
|
+
end
|
198
|
+
|
199
|
+
def set_initial_origin_name(initial_origin_name)
|
200
|
+
@origin_names = [initial_origin_name]
|
201
|
+
end
|
202
|
+
|
203
|
+
def set_initial_origin_names(initial_origin_names)
|
204
|
+
@origin_names = initial_origin_names
|
205
|
+
end
|
206
|
+
|
207
|
+
def count
|
208
|
+
list.size
|
209
|
+
end
|
210
|
+
|
211
|
+
# Get the maximum item in the list, equivalent to calling LIST_MAX(list) in ink.
|
212
|
+
def max_item
|
213
|
+
list.max do |a, b|
|
214
|
+
return -1 if a[0].null_item?
|
215
|
+
return 1 if b[0].null_item?
|
216
|
+
a[1] <=> b[1]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Get the minimum item in the list, equivalent to calling LIST_MIN(list) in ink.
|
221
|
+
def min_item
|
222
|
+
list.min do |a, b|
|
223
|
+
return -1 if a[0].null_item?
|
224
|
+
return 1 if b[0].null_item?
|
225
|
+
a[1] <=> b[1]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# The inverse of the list, equivalent to colling LIST_INVERSE(list) in ink.
|
230
|
+
def inverse
|
231
|
+
new_list = self.class.new
|
232
|
+
origins.each do |origin|
|
233
|
+
origin.items.each do |item, int_value|
|
234
|
+
if self.list.none?{|item_to_search, other_value| item_to_search.equal?(item)}
|
235
|
+
new_list.list[item] = int_value
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
new_list
|
241
|
+
end
|
242
|
+
|
243
|
+
# The list of all items from the original list definition, equivalent to
|
244
|
+
# calling LIST_ALL(list) in ink.
|
245
|
+
def all
|
246
|
+
new_list = self.class.new
|
247
|
+
origins.each do |origin|
|
248
|
+
origin.items.each do |item, int_value|
|
249
|
+
new_list.list[item] = int_value
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
new_list
|
254
|
+
end
|
255
|
+
|
256
|
+
# Return a new list that is a combination of the current list and
|
257
|
+
# one that's passed in. The equivalent of calling (list1 + list2) in ink.
|
258
|
+
def +(other_list)
|
259
|
+
union_list = self.class.copy_list(self)
|
260
|
+
other_list.list.each do |item, int_value|
|
261
|
+
union_list.list[item] = int_value
|
262
|
+
end
|
263
|
+
|
264
|
+
return union_list
|
265
|
+
end
|
266
|
+
|
267
|
+
# Return a new list that is the intersection of the current list and
|
268
|
+
# one that's passed in. The equivalent of calling (list1 ^ list2) in ink.
|
269
|
+
def &(other_list)
|
270
|
+
intersection_list = self.class.new
|
271
|
+
self.list.each do |item, int_value|
|
272
|
+
if other_list.list.any?{|other_item, other_value| other_item.equal?(item)}
|
273
|
+
intersection_list.list[item] = int_value
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
return intersection_list
|
278
|
+
end
|
279
|
+
|
280
|
+
# Returns a new list that's the same as the current one, except with the
|
281
|
+
# given items removed that are in the passed-in list. Equivalent to calling
|
282
|
+
# (list1 - list2) in ink.
|
283
|
+
def -(other_list)
|
284
|
+
without_list = self.class.copy_list(self)
|
285
|
+
other_list.list.each do |item, int_value|
|
286
|
+
without_list.list.delete_if{ |without_list_item, other_value| without_list_item.equal?(item)}
|
287
|
+
end
|
288
|
+
|
289
|
+
return without_list
|
290
|
+
end
|
291
|
+
|
292
|
+
# Returns true if the current list contains all the items that are in the
|
293
|
+
# list that is passed in. Equivalent to calling (list1 ? list2) in ink.
|
294
|
+
def contains?(other_list)
|
295
|
+
other_list.list.all?{|other_item, other_value| self.list.any?{|item, value| other_item.equal?(item) }}
|
296
|
+
end
|
297
|
+
|
298
|
+
# Returns true if all the item values in the current list are greater than
|
299
|
+
# all the item values in the passed-in list. Equivalent to calling
|
300
|
+
# (list1 > list2) in ink.
|
301
|
+
def >(other_list)
|
302
|
+
return false if self.list.empty?
|
303
|
+
return true if other_list.list.empty?
|
304
|
+
|
305
|
+
return self.min_item[1] > other_list.max_item[1]
|
306
|
+
end
|
307
|
+
|
308
|
+
# Returns true if the item values in the current list overlap, or are all
|
309
|
+
# greater than the item values in the passed-in list. None of the item values
|
310
|
+
# in the current list must fall below the item values in the passed-in list
|
311
|
+
# Equivalent to (list1 >= list2) in ink, or LIST_MIN(list1) >= LIST_MIN(list2) &&
|
312
|
+
# LIST_MAX(list1) >= LIST_MAX(list2)
|
313
|
+
def >=(other_list)
|
314
|
+
return false if self.list.empty?
|
315
|
+
return true if other_list.list.empty?
|
316
|
+
|
317
|
+
return (
|
318
|
+
self.min_item[1] >= other_list.min_item[1] &&
|
319
|
+
self.max_item[1] >= other_list.max_item[1]
|
320
|
+
)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Returns true if all the item values in the current list are less than all the
|
324
|
+
# item values in the passed-in list. Equivalent to calling (list1 < list2 in ink)
|
325
|
+
def <(other_list)
|
326
|
+
return false if other_list.list.empty?
|
327
|
+
return true if self.list.empty?
|
328
|
+
|
329
|
+
return self.max_item[1] < other_list.min_item[1]
|
330
|
+
end
|
331
|
+
|
332
|
+
# Returns true if the item values in the current list overlap, or are all less than
|
333
|
+
# the item values in the passed in list. None of the item values in the current list
|
334
|
+
# must go above the item values in the passed in list. Equivalent to (list1 <= list2)
|
335
|
+
# in ink, or LIST_MAX(list1) <= LIST_MAX(list2) && LIST_MIN(list1) <= LIST_MIN(list2)
|
336
|
+
def <=(other_list)
|
337
|
+
return false if other_list.list.empty?
|
338
|
+
return true if self.list.empty?
|
339
|
+
|
340
|
+
return (
|
341
|
+
self.max_item[1] <= other_list.max_item[1] &&
|
342
|
+
self.min_item[1] <= other_list.min_item[1]
|
343
|
+
)
|
344
|
+
end
|
345
|
+
|
346
|
+
def max_as_list
|
347
|
+
if self.list.empty?
|
348
|
+
return self.class.new
|
349
|
+
else
|
350
|
+
return self.class.new_with_single_item(max_item)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def min_as_list
|
355
|
+
if self.list.empty?
|
356
|
+
return self.class.new
|
357
|
+
else
|
358
|
+
return self.class.new_with_single_item(min_item)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# Returns a sublist with the elements given in the minimum & maximum
|
363
|
+
# bounds. The bounds can either be ints, which are indicies into the entire (sorted)
|
364
|
+
# list, or they can be InkLists themsevles. These are intended to be single-item lists,
|
365
|
+
# so you can specify the upper & lower bounds. If you pass in multi-item lists, it'll use
|
366
|
+
# the minimum and maximum items in those lists, respectively.
|
367
|
+
def list_with_subrange(min_bound, max_bound)
|
368
|
+
return self.class.new if self.list.empty?
|
369
|
+
|
370
|
+
ordered = self.ordered_items
|
371
|
+
|
372
|
+
min_value = 0
|
373
|
+
max_value = Float::INFINITY
|
374
|
+
|
375
|
+
if min_bound.is_a?(Numeric)
|
376
|
+
min_value = min_bound
|
377
|
+
elsif min_bound.is_a?(InkList) && !min_bound.list.empty?
|
378
|
+
min_value = min_bound.min_item[1]
|
379
|
+
end
|
380
|
+
|
381
|
+
if max_bound.is_a?(Numeric)
|
382
|
+
max_value = max_bound
|
383
|
+
elsif max_bound.is_a?(InkList) && !max_bound.list.empty?
|
384
|
+
max_value = max_bound.max_item[1]
|
385
|
+
end
|
386
|
+
|
387
|
+
sublist = self.class.new
|
388
|
+
sublist.set_initial_origin_names(origin_names)
|
389
|
+
ordered.each do |item, int_value|
|
390
|
+
if int_value >= min_value && int_value <= max_value
|
391
|
+
sublist.list[item] = int_value
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
sublist
|
396
|
+
end
|
397
|
+
|
398
|
+
# Returns true if the passed object is also an ink list that contains the
|
399
|
+
# same items as the current list, false otherwise.
|
400
|
+
def ==(other_list)
|
401
|
+
return false if !other_list.is_a?(InkList)
|
402
|
+
return false if other_list.list.size != self.list.size
|
403
|
+
|
404
|
+
return self.list.all?{|item, int_value| other_list.list.has_key?(item) }
|
405
|
+
end
|
406
|
+
|
407
|
+
def ordered_items
|
408
|
+
self.list.sort do |a, b|
|
409
|
+
# ensure consistent ordering of mixed lists
|
410
|
+
if(a[1] == b[1])
|
411
|
+
a[0].origin_name <=> b[0].origin_name
|
412
|
+
else
|
413
|
+
a[1] <=> b[1]
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
# Returns a string in the form "a, b, c" with the names of the items in the
|
419
|
+
# list, without the origin list definition names. Equivalent to writing
|
420
|
+
# {list} in ink
|
421
|
+
def to_s
|
422
|
+
ordered_items.map{|item, int_value| item.item_name }.join(", ")
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|