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,44 @@
1
+ module Fable
2
+ class ListDefinition
3
+ attr_accessor :name, :items
4
+
5
+ # The main representation should be simple item names, rather than a
6
+ # RawListItem, since we mainly want to access items based on their
7
+ # simple name, since that's how they'll be most commonly requested
8
+ # from ink
9
+ attr_accessor :item_name_to_values
10
+
11
+ def items
12
+ if @items.nil?
13
+ @items = {}
14
+ @item_name_to_values.each do |key, value|
15
+ item = InkList::InkListItem.new(origin_name: name, item_name: key)
16
+ @items[item] = value
17
+ end
18
+ end
19
+ @items
20
+ end
21
+
22
+ def value_for_item(item)
23
+ return item_name_to_values[item.item_name] || 0
24
+ end
25
+
26
+ def contains?(item)
27
+ return false if item.origin_name != self.name
28
+ return contains_item_with_name?(item.item_name)
29
+ end
30
+
31
+ def contains_item_with_name?(item_name)
32
+ return item_name_to_values.has_key?(item_name)
33
+ end
34
+
35
+ def item_for_value(int_value)
36
+ return item_name_to_values.key(int_value)
37
+ end
38
+
39
+ def initialize(name, items)
40
+ self.name = name
41
+ self.item_name_to_values = items
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,35 @@
1
+ module Fable
2
+ class ListDefinitionsOrigin
3
+ attr_accessor :_lists, :all_unambiguous_list_value_cache
4
+
5
+ def lists
6
+ self._lists.map{|k,v| v}
7
+ end
8
+
9
+ def find_list(name)
10
+ self._lists[name]
11
+ end
12
+
13
+ def initialize(lists)
14
+ self._lists = {}
15
+ self.all_unambiguous_list_value_cache = {}
16
+
17
+ lists.each do |list|
18
+ self._lists[list.name] = list
19
+
20
+ list.items.each do |item, int_value|
21
+ list_value = ListValue.new(item, int_value)
22
+
23
+ # May be ambiguous, but compiler should've caught that,
24
+ # so we may be doing some replacement here, but that's okay
25
+ self.all_unambiguous_list_value_cache[item.item_name] = list_value
26
+ self.all_unambiguous_list_value_cache[item.full_name] = list_value
27
+ end
28
+ end
29
+ end
30
+
31
+ def find_single_item_list_with_name(name)
32
+ return all_unambiguous_list_value_cache[name]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,324 @@
1
+ module Fable
2
+ class NativeFunctionCall < RuntimeObject
3
+ extend NativeFunctionOperations
4
+
5
+ FUNCTIONS = {
6
+ # native functions
7
+ ADDITION: "+",
8
+ SUBTRACTION: "-",
9
+ DIVIDE: "/",
10
+ MULTIPLY: "*",
11
+ MODULO: "%",
12
+ NEGATE: "_",
13
+
14
+ EQUALS: "==",
15
+ GREATER_THAN: ">",
16
+ LESS_THAN: "<",
17
+ GREATER_THAN_OR_EQUAL_TO: ">=",
18
+ LESS_THAN_OR_EQUAL_TO: "<=",
19
+ NOT_EQUAL: "!=",
20
+ NOT: "!",
21
+
22
+
23
+ AND: "&&",
24
+ OR: "||",
25
+
26
+ MIN: "MIN",
27
+ MAX: "MAX",
28
+
29
+ POWER: "POW",
30
+ FLOOR: "FLOOR",
31
+ CEILING: "CEILING",
32
+ INT_VALUE: "INT",
33
+ FLOAT_VALUE: "FLOAT",
34
+
35
+ HAS: "?",
36
+ HAS_NOT: "!?",
37
+ INTERSECTION: "^",
38
+
39
+ LIST_MINIMUM: "LIST_MIN",
40
+ LIST_MAXIMUM: "LIST_MAX",
41
+ LIST_ALL: "LIST_ALL",
42
+ LIST_COUNT: "LIST_COUNT",
43
+ VALUE_OF_LIST: "LIST_VALUE",
44
+ LIST_INVERT: "LIST_INVERT",
45
+ }.freeze
46
+
47
+ NUMBER_OF_PARAMETERS = {
48
+ ADDITION: 2,
49
+ SUBTRACTION: 2,
50
+ DIVIDE: 2,
51
+ MULTIPLY: 2,
52
+ MODULO: 2,
53
+ NEGATE: 1,
54
+
55
+ EQUALS: 2,
56
+ GREATER_THAN: 2,
57
+ LESS_THAN: 2,
58
+ GREATER_THAN_OR_EQUAL_TO: 2,
59
+ LESS_THAN_OR_EQUAL_TO: 2,
60
+ NOT_EQUAL: 2,
61
+ NOT: 1,
62
+
63
+
64
+ AND: 2,
65
+ OR: 2,
66
+
67
+ MIN: 2,
68
+ MAX: 2,
69
+
70
+ POWER: 2,
71
+ FLOOR: 1,
72
+ CEILING: 1,
73
+ INT_VALUE: 1,
74
+ FLOAT_VALUE: 1,
75
+
76
+ HAS: 2,
77
+ HAS_NOT: 2,
78
+ INTERSECTION: 2,
79
+
80
+ LIST_MINIMUM: 1,
81
+ LIST_MAXIMUM: 1,
82
+ LIST_ALL: 1,
83
+ LIST_COUNT: 1,
84
+ VALUE_OF_LIST: 1,
85
+ LIST_INVERT: 1,
86
+ }.freeze
87
+
88
+ LOOKUP = FUNCTIONS.invert.freeze
89
+
90
+ attr_accessor :name, :number_of_parameters
91
+
92
+ def self.is_native_function?(value)
93
+ LOOKUP.has_key?(value)
94
+ end
95
+
96
+ def initialize(function_symbol)
97
+ super()
98
+ self.name = LOOKUP[function_symbol]
99
+ self.number_of_parameters = NUMBER_OF_PARAMETERS[self.name]
100
+ end
101
+
102
+ def call!(parameters)
103
+ if parameters.size != self.number_of_parameters
104
+ raise StoryError, "Unexpected number of parameters"
105
+ end
106
+
107
+ has_list = false
108
+
109
+ parameters.each do |parameter|
110
+ case parameter
111
+ when Void
112
+ raise StoryError, "Attempting to perform operation on a void value. Did you forget to 'return' a value from a function you called here?"
113
+ when ListValue
114
+ has_list = true
115
+ end
116
+ end
117
+
118
+ # Binary operations on lists are treated outside of the standard coercion rules
119
+ if parameters.size == 2 && has_list
120
+ return call_binary_list_operation(parameters)
121
+ end
122
+
123
+ coerced_parameters = coerce_values_to_single_type(parameters)
124
+
125
+ return underlying_function_call(coerced_parameters)
126
+ end
127
+
128
+ def underlying_function_call(parameters)
129
+ parameter_1 = parameters.first
130
+ value_type = parameter_1.class
131
+
132
+ if parameters.size > 2
133
+ raise Error, "Unexpected number of parameters to NativeFunctionCall: #{parameters.size}"
134
+ end
135
+
136
+ # if !can_perform_function_on?(value_type)
137
+ # raise Error, "Cannot perform operation '#{self.name}' on #{value_type}"
138
+ # end
139
+
140
+ # Binary function
141
+ if parameters.size == 2
142
+ parameter_2 = parameters.last
143
+ result = run_operation(parameter_1.value, parameter_2.value)
144
+
145
+ return Value.create(result)
146
+ # Unary Function
147
+ else
148
+ result = run_operation(parameter_1.value)
149
+ return Value.create(result)
150
+ end
151
+ end
152
+
153
+ def call_binary_list_operation(parameters)
154
+ value_1 = parameters[0]
155
+ value_2 = parameters[1]
156
+
157
+ # List-Int addition/subtraction returns a List (eg: "alpha" + 1 = "beta")
158
+ if (name == :ADDITION || name == :SUBTRACTION) && value_1.is_a?(ListValue) && value_2.is_a?(IntValue)
159
+ return call_list_increment_operation(parameters)
160
+ end
161
+
162
+ # And/or with any other types required coercion to bool (int)
163
+ if (name == :AND || :OR) && (!value_1.is_a?(ListValue) || !value_2.is_a?(ListValue))
164
+ value_1_as_boolean = value_1.truthy? ? 1 : 0
165
+ value_2_as_boolean = value_2.truthy? ? 1 : 0
166
+
167
+ result = run_operation(value_1_as_boolean, value_2_as_boolean)
168
+ return IntValue.new(result)
169
+ end
170
+
171
+ # Normal (list X list) operation
172
+ if value_1.is_a?(ListValue) && value_2.is_a?(ListValue)
173
+ if name == :HAS || name == :HAS_NOT
174
+ return run_operation(value_1.value, value_2.value)
175
+ else
176
+ result = run_operation(value_1.value, value_2.value)
177
+ return ListValue.new(result)
178
+ end
179
+ end
180
+
181
+ raise Error, "Can not call '#{name}' operation on '#{value_1.class}' and '#{value_2.class}'"
182
+ end
183
+
184
+ def call_list_increment_operation(parameters)
185
+ list_value = parameters[0]
186
+ int_value = parameters[1]
187
+
188
+ result_list = InkList.new
189
+
190
+ list_value.value.items.each do |list_item, list_item_value|
191
+ target_integer = run_operation(list_item_value, int_value.value)
192
+
193
+ # Find this item's origin
194
+ item_origin = list_value.value.origins.find{|origin| origin.name == list_item.origin_name }
195
+
196
+ if !item_origin.nil?
197
+ incremented_item = item_origin.item_for_value(target_integer)
198
+ if !incremented_item.nil?
199
+ result_list.add(incremented_item, target_integer)
200
+ end
201
+ end
202
+ end
203
+
204
+ return ListValue.new(result_list)
205
+ end
206
+
207
+ def coerce_values_to_single_type(parameters)
208
+ given_types = parameters.map(&:class)
209
+ special_case_list = parameters.find{|x| x.is_a?(ListValue) }
210
+
211
+ # Find out what the output type is; "higher-level" types infect both
212
+ # so that binary operations use the same type on both sides
213
+ # (eg: binary operation of int & float causes the int to be casted as a float)
214
+ value_type = ([IntValue] + given_types).max{|a,b| OrderedValueTypes[a] <=> OrderedValueTypes[b] }
215
+
216
+ # Coerce to this chosen type
217
+ parameters_out = []
218
+
219
+ # Special case: Coercing Ints to Lists
220
+ # We have to do it early when we have both parameters
221
+ # to hand, so that we can make use of the list's origin
222
+ if value_type == ListValue
223
+ parameters.each do |parameter|
224
+ if parameter.is_a?(ListValue)
225
+ parameters_out << parameter
226
+ elsif parameter.is_a?(IntValue)
227
+ int_value = parameter.value
228
+ list = special_case_list.value.origin_of_max_item
229
+
230
+ item = list.item_for_value(int_value)
231
+
232
+ if !item.nil?
233
+ parameters_out << ListValue.new(item, int_value)
234
+ else
235
+ raise Error, "Could not find List item with the value '#{int_value}' in #{list.name}"
236
+ end
237
+ else
238
+ raise Error, "Cannot mix Lists and #{parameter.class} values in this operation"
239
+ end
240
+ end
241
+ # Normal coercing, with standard casting
242
+ else
243
+ parameters.each do |parameter|
244
+ parameters_out << parameter.cast(value_type)
245
+ end
246
+ end
247
+
248
+ return parameters_out
249
+ end
250
+
251
+ def to_s
252
+ "Native '#{name}'"
253
+ end
254
+
255
+ protected
256
+
257
+ def run_operation(*parameters)
258
+ case name
259
+ when :ADDITION
260
+ return self.class.addition(parameters[1], parameters[0])
261
+ when :SUBTRACTION
262
+ return self.class.subtraction(parameters[1], parameters[0])
263
+ when :DIVIDE
264
+ return self.class.divide(parameters[1], parameters[0])
265
+ when :MULTIPLY
266
+ return self.class.multiply(parameters[1], parameters[0])
267
+ when :MODULO
268
+ return self.class.modulo(parameters[1], parameters[0])
269
+ when :NEGATE
270
+ return self.class.negate(parameter[0])
271
+ when :EQUALS
272
+ return self.class.equal(parameters[1], parameters[0])
273
+ when :GREATER_THAN
274
+ return self.class.greater(parameters[1], parameters[0])
275
+ when :LESS_THAN
276
+ return self.class.less(parameters[1], parameters[0])
277
+ when :GREATER_THAN_OR_EQUAL_TO
278
+ return self.class.greater_than_or_equal(parameters[1], parameters[0])
279
+ when :LESS_THAN_OR_EQUAL_TO
280
+ return self.class.less_than_or_equal(parameters[1], parameters[0])
281
+ when :NOT_EQUAL
282
+ return self.class.not_equal(parameters[1], parameters[0])
283
+ when :NOT
284
+ return self.class.not(parameters[0])
285
+ when :AND
286
+ return self.class.and(parameters[1], parameters[0])
287
+ when :OR
288
+ return self.class.or(parameters[1], parameters[0])
289
+ when :MIN
290
+ return self.class.min(parameters[1], parameters[0])
291
+ when :MAX
292
+ return self.class.max(parameters[1], parameters[0])
293
+ when :POWER
294
+ return self.class.pow(parameters[1], parameters[0])
295
+ when :FLOOR
296
+ return self.class.floor(parameters[0])
297
+ when :CEILING
298
+ return self.class.ceiling(parameters[0])
299
+ when :INT_VALUE
300
+ return self.class.int_value(parameters[0])
301
+ when :FLOAT_VALUE
302
+ return self.class.float_value(parameters[0])
303
+ when :HAS
304
+ return self.class.has(parameters[1], parameters[0])
305
+ when :HAS_NOT
306
+ return self.class.has_not(parameters[1], parameters[0])
307
+ when :INTERSECTION
308
+ return self.class.intersection(parameters[1], parameters[0])
309
+ when :LIST_MINIMUM
310
+ return self.class.list_min(parameters[0])
311
+ when :LIST_MAXIMUM
312
+ return self.class.list_max(parameters[0])
313
+ when :LIST_ALL
314
+ return self.class.all(parameters[0])
315
+ when :LIST_COUNT
316
+ return self.class.count(parameters[0])
317
+ when :VALUE_OF_LIST
318
+ return self.class.value_of_list(parameters[0])
319
+ when :LIST_INVERT
320
+ return self.class.invert(parameters[0])
321
+ end
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,149 @@
1
+ module Fable
2
+ module NativeFunctionOperations
3
+ def addition(x, y)
4
+ return x + y
5
+ end
6
+
7
+ def subtraction(x,y)
8
+ return x - y
9
+ end
10
+
11
+ def multiply(x,y)
12
+ return x * y
13
+ end
14
+
15
+ def divide(x,y)
16
+ x / y
17
+ end
18
+
19
+ def modulo(x,y)
20
+ return x % y
21
+ end
22
+
23
+ def negate(x)
24
+ return -x
25
+ end
26
+
27
+ def equal(x,y)
28
+ return (x == y) ? 1 : 0
29
+ end
30
+
31
+ def greater(x,y)
32
+ return (x > y) ? 1 : 0
33
+ end
34
+
35
+ def less(x,y)
36
+ return (x < y) ? 1 : 0
37
+ end
38
+
39
+ def greater_than_or_equal(x,y)
40
+ return (x >= y) ? 1 : 0
41
+ end
42
+
43
+ def less_than_or_equal(x,y)
44
+ return (x <= y) ? 1 : 0
45
+ end
46
+
47
+ def not_equal(x,y)
48
+ return (x != y) ? 1 : 0
49
+ end
50
+
51
+ def not(x)
52
+ if x.is_a?(InkList)
53
+ return (x.size == 0) ? 1 : 0
54
+ else
55
+ return (x == 0) ? 1 : 0
56
+ end
57
+ end
58
+
59
+ def and(x,y)
60
+ if x.is_a?(InkList)
61
+ return (x.size > 0 && y.size > 0) ? 1 : 0
62
+ else
63
+ return (x != 0 && y != 0) ? 1 : 0
64
+ end
65
+ end
66
+
67
+ def or(x,y)
68
+ if x.is_a?(InkList)
69
+ return (x.size > 0 || y.size > 0) ? 1 : 0
70
+ else
71
+ return (x != 0 || y != 0) ? 1 : 0
72
+ end
73
+ end
74
+
75
+ def max(x,y)
76
+ return [x,y].max
77
+ end
78
+
79
+ def min(x,y)
80
+ return [x,y].min
81
+ end
82
+
83
+ def pow(x,y)
84
+ return x ** y
85
+ end
86
+
87
+ def floor(x)
88
+ x.floor.to_f
89
+ end
90
+
91
+ def ceiling(x)
92
+ x.ceil.to_f
93
+ end
94
+
95
+ def int_value(x)
96
+ x.to_i
97
+ end
98
+
99
+ def float_value(x)
100
+ x.to_f
101
+ end
102
+
103
+ def has(x,y)
104
+ if x.is_a?(InkList)
105
+ return x.contains?(y) ? 1 : 0
106
+ else
107
+ return x.include?(y) ? 1 : 0
108
+ end
109
+ end
110
+
111
+ def has_not(x,y)
112
+ has_return = has(x,y)
113
+ # Need to invert values
114
+ if has_return == 0
115
+ return 1
116
+ else
117
+ return 0
118
+ end
119
+ end
120
+
121
+ def intersection(x,y)
122
+ return x & y
123
+ end
124
+
125
+ def invert(x)
126
+ x.inverse
127
+ end
128
+
129
+ def all(x)
130
+ x.all
131
+ end
132
+
133
+ def list_min(x)
134
+ x.min_as_list
135
+ end
136
+
137
+ def list_max(x)
138
+ x.max_as_list
139
+ end
140
+
141
+ def count(x)
142
+ x.count
143
+ end
144
+
145
+ def value_of_list(x)
146
+ x.max_item[1]
147
+ end
148
+ end
149
+ end