fable 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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