Class: Fable::NativeFunctionCall

Inherits:
RuntimeObject show all
Extended by:
NativeFunctionOperations
Defined in:
lib/fable/native_function_call.rb

Constant Summary collapse

FUNCTIONS =
{
  # native functions
  ADDITION: "+",
  SUBTRACTION: "-",
  DIVIDE: "/",
  MULTIPLY: "*",
  MODULO: "%",
  NEGATE: "_",

  EQUALS: "==",
  GREATER_THAN: ">",
  LESS_THAN: "<",
  GREATER_THAN_OR_EQUAL_TO: ">=",
  LESS_THAN_OR_EQUAL_TO: "<=",
  NOT_EQUAL: "!=",
  NOT: "!",


  AND: "&&",
  OR: "||",

  MIN: "MIN",
  MAX: "MAX",

  POWER: "POW",
  FLOOR: "FLOOR",
  CEILING: "CEILING",
  INT_VALUE: "INT",
  FLOAT_VALUE: "FLOAT",

  HAS: "?",
  HAS_NOT: "!?",
  INTERSECTION: "^",

  LIST_MINIMUM: "LIST_MIN",
  LIST_MAXIMUM: "LIST_MAX",
  LIST_ALL:     "LIST_ALL",
  LIST_COUNT:   "LIST_COUNT",
  VALUE_OF_LIST: "LIST_VALUE",
  LIST_INVERT:  "LIST_INVERT",
}.freeze
NUMBER_OF_PARAMETERS =
{
  ADDITION: 2,
  SUBTRACTION: 2,
  DIVIDE: 2,
  MULTIPLY: 2,
  MODULO: 2,
  NEGATE: 1,

  EQUALS: 2,
  GREATER_THAN: 2,
  LESS_THAN: 2,
  GREATER_THAN_OR_EQUAL_TO: 2,
  LESS_THAN_OR_EQUAL_TO: 2,
  NOT_EQUAL: 2,
  NOT: 1,


  AND: 2,
  OR: 2,

  MIN: 2,
  MAX: 2,

  POWER: 2,
  FLOOR: 1,
  CEILING: 1,
  INT_VALUE: 1,
  FLOAT_VALUE: 1,

  HAS: 2,
  HAS_NOT: 2,
  INTERSECTION: 2,

  LIST_MINIMUM: 1,
  LIST_MAXIMUM: 1,
  LIST_ALL:     1,
  LIST_COUNT:   1,
  VALUE_OF_LIST: 1,
  LIST_INVERT:  1,
}.freeze
LOOKUP =
FUNCTIONS.invert.freeze

Instance Attribute Summary collapse

Attributes inherited from RuntimeObject

#original_object, #own_debug_metadata, #parent, #path

Class Method Summary collapse

Instance Method Summary collapse

Methods included from NativeFunctionOperations

addition, all, and, ceiling, count, divide, equal, float_value, floor, greater, greater_than_or_equal, has, has_not, int_value, intersection, invert, less, less_than_or_equal, list_max, list_min, max, min, modulo, multiply, negate, not, not_equal, or, pow, subtraction, value_of_list

Methods inherited from RuntimeObject

#compact_path_string, #convert_path_to_relative, #copy, #debug_line_number_of_path, #debug_metadata, #indentation_string, #resolve_path, #root_content_container

Constructor Details

#initialize(function_symbol) ⇒ NativeFunctionCall

Returns a new instance of NativeFunctionCall.



96
97
98
99
100
# File 'lib/fable/native_function_call.rb', line 96

def initialize(function_symbol)
  super()
  self.name = LOOKUP[function_symbol]
  self.number_of_parameters = NUMBER_OF_PARAMETERS[self.name]
end

Instance Attribute Details

#nameObject

Returns the value of attribute name.



90
91
92
# File 'lib/fable/native_function_call.rb', line 90

def name
  @name
end

#number_of_parametersObject

Returns the value of attribute number_of_parameters.



90
91
92
# File 'lib/fable/native_function_call.rb', line 90

def number_of_parameters
  @number_of_parameters
end

Class Method Details

.is_native_function?(value) ⇒ Boolean

Returns:

  • (Boolean)


92
93
94
# File 'lib/fable/native_function_call.rb', line 92

def self.is_native_function?(value)
  LOOKUP.has_key?(value)
end

Instance Method Details

#call!(parameters) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/fable/native_function_call.rb', line 102

def call!(parameters)
  if parameters.size != self.number_of_parameters
    raise StoryError, "Unexpected number of parameters"
  end

  has_list = false

  parameters.each do |parameter|
    case parameter
    when Void
      raise StoryError, "Attempting to perform operation on a void value. Did you forget to 'return' a value from a function you called here?"
    when ListValue
      has_list = true
    end
  end

  # Binary operations on lists are treated outside of the standard coercion rules
  if parameters.size == 2 && has_list
    return call_binary_list_operation(parameters)
  end

  coerced_parameters = coerce_values_to_single_type(parameters)

  return underlying_function_call(coerced_parameters)
end

#call_binary_list_operation(parameters) ⇒ Object

Raises:



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/fable/native_function_call.rb', line 153

def call_binary_list_operation(parameters)
  value_1 = parameters[0]
  value_2 = parameters[1]

  # List-Int addition/subtraction returns a List (eg: "alpha" + 1 = "beta")
  if (name == :ADDITION || name == :SUBTRACTION) && value_1.is_a?(ListValue) && value_2.is_a?(IntValue)
    return call_list_increment_operation(parameters)
  end

  # And/or with any other types required coercion to bool (int)
  if (name == :AND || :OR) && (!value_1.is_a?(ListValue) || !value_2.is_a?(ListValue))
    value_1_as_boolean = value_1.truthy? ? 1 : 0
    value_2_as_boolean = value_2.truthy? ? 1 : 0

    result = run_operation(value_1_as_boolean, value_2_as_boolean)
    return IntValue.new(result)
  end

  # Normal (list X list) operation
  if value_1.is_a?(ListValue) && value_2.is_a?(ListValue)
    if name == :HAS || name == :HAS_NOT
      return run_operation(value_1.value, value_2.value)
    else
      result = run_operation(value_1.value, value_2.value)
      return ListValue.new(result)
    end
  end

  raise Error, "Can not call '#{name}' operation on '#{value_1.class}' and '#{value_2.class}'"
end

#call_list_increment_operation(parameters) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/fable/native_function_call.rb', line 184

def call_list_increment_operation(parameters)
  list_value = parameters[0]
  int_value = parameters[1]

  result_list = InkList.new

  list_value.value.items.each do |list_item, list_item_value|
    target_integer = run_operation(list_item_value, int_value.value)

    # Find this item's origin
    item_origin = list_value.value.origins.find{|origin| origin.name == list_item.origin_name }

    if !item_origin.nil?
      incremented_item = item_origin.item_for_value(target_integer)
      if !incremented_item.nil?
        result_list.add(incremented_item, target_integer)
      end
    end
  end

  return ListValue.new(result_list)
end

#coerce_values_to_single_type(parameters) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/fable/native_function_call.rb', line 207

def coerce_values_to_single_type(parameters)
  given_types = parameters.map(&:class)
  special_case_list = parameters.find{|x| x.is_a?(ListValue) }

  # Find out what the output type is; "higher-level" types infect both
  # so that binary operations use the same type on both sides
  # (eg: binary operation of int & float causes the int to be casted as a float)
  value_type = ([IntValue] + given_types).max{|a,b| OrderedValueTypes[a] <=> OrderedValueTypes[b] }

  # Coerce to this chosen type
  parameters_out = []

  # Special case: Coercing Ints to Lists
  # We have to do it early when we have both parameters
  # to hand, so that we can make use of the list's origin
  if value_type == ListValue
    parameters.each do |parameter|
      if parameter.is_a?(ListValue)
        parameters_out << parameter
      elsif parameter.is_a?(IntValue)
        int_value = parameter.value
        list = special_case_list.value.origin_of_max_item

        item = list.item_for_value(int_value)

        if !item.nil?
          parameters_out << ListValue.new(item, int_value)
        else
          raise Error, "Could not find List item with the value '#{int_value}' in #{list.name}"
        end
      else
        raise Error, "Cannot mix Lists and #{parameter.class} values in this operation"
      end
    end
  # Normal coercing, with standard casting
  else
    parameters.each do |parameter|
      parameters_out << parameter.cast(value_type)
    end
  end

  return parameters_out
end

#to_sObject



251
252
253
# File 'lib/fable/native_function_call.rb', line 251

def to_s
  "Native '#{name}'"
end

#underlying_function_call(parameters) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/fable/native_function_call.rb', line 128

def underlying_function_call(parameters)
  parameter_1 = parameters.first
  value_type = parameter_1.class

  if parameters.size > 2
    raise Error, "Unexpected number of parameters to NativeFunctionCall: #{parameters.size}"
  end

  # if !can_perform_function_on?(value_type)
  #   raise Error, "Cannot perform operation '#{self.name}' on #{value_type}"
  # end

  # Binary function
  if parameters.size == 2
    parameter_2 = parameters.last
    result = run_operation(parameter_1.value, parameter_2.value)

    return Value.create(result)
  # Unary Function
  else
    result = run_operation(parameter_1.value)
    return Value.create(result)
  end
end