Class: Q_Environment

Inherits:
Object
  • Object
show all
Defined in:
lib/q-language/environment.rb

Overview

Copyright © 2010-2011 Jesse Sielaff

Constant Summary collapse

TooManyNodes =
Class.new(Exception)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(variables = {}, *implicit, object, options) ⇒ Q_Environment

Returns a new instance of Q_Environment.



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/q-language/environment.rb', line 7

def initialize (variables = {}, *implicit, object, options)
  @successful_methods = Hash.new {|h,k| h[k] = 0 }
  @failed_methods = Hash.new {|h,k| h[k] = 0 }
  @discarded_objects = Hash.new {|h,k| h[k] = 0 }
  @nodes_evaluated = Hash.new {|h,k| h[k] = 0 }
  
  @max_nodes = options[0]
  @max_method_nodes = options[1]
  
  @max_array_length = options[2]
  @max_hash_length = options[3]
  @max_string_length = options[4]
  
  @variables = variables.to_hash
  @implicit = implicit
  @frame_stack = [object]
  
  @scope = { queue: [], method_stack: [], unassigned_variables: [], prev: nil }
  @method_results = []
end

Instance Attribute Details

#discarded_objectsObject (readonly)

Returns the value of attribute discarded_objects.



32
33
34
# File 'lib/q-language/environment.rb', line 32

def discarded_objects
  @discarded_objects
end

#failed_methodsObject (readonly)

Returns the value of attribute failed_methods.



32
33
34
# File 'lib/q-language/environment.rb', line 32

def failed_methods
  @failed_methods
end

#max_array_lengthObject

Returns the value of attribute max_array_length.



31
32
33
# File 'lib/q-language/environment.rb', line 31

def max_array_length
  @max_array_length
end

#max_hash_lengthObject

Returns the value of attribute max_hash_length.



31
32
33
# File 'lib/q-language/environment.rb', line 31

def max_hash_length
  @max_hash_length
end

#max_method_nodesObject

Returns the value of attribute max_method_nodes.



30
31
32
# File 'lib/q-language/environment.rb', line 30

def max_method_nodes
  @max_method_nodes
end

#max_nodesObject

Returns the value of attribute max_nodes.



30
31
32
# File 'lib/q-language/environment.rb', line 30

def max_nodes
  @max_nodes
end

#max_string_lengthObject

Returns the value of attribute max_string_length.



31
32
33
# File 'lib/q-language/environment.rb', line 31

def max_string_length
  @max_string_length
end

#nodes_evaluatedObject (readonly)

Returns the value of attribute nodes_evaluated.



32
33
34
# File 'lib/q-language/environment.rb', line 32

def nodes_evaluated
  @nodes_evaluated
end

#successful_methodsObject (readonly)

Returns the value of attribute successful_methods.



32
33
34
# File 'lib/q-language/environment.rb', line 32

def successful_methods
  @successful_methods
end

Instance Method Details

#arg_search_block(q_class, indices) ⇒ Object

Returns a Proc used for iterating over objects in the queue while looking for QObject method arguments. The returned Proc uses two parameters: an object from the queue, and that object’s index in the queue. If the object parameter is an instance of the given QObject class and has not yet been used as the method receiver or as a method argument, the Proc adds the index parameter to the given indices Array and marks the object’s index in the queue as used.



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/q-language/environment.rb', line 42

def arg_search_block (q_class, indices)
  proc do |object, i|
    next if (i == @receiver_index) or @arg_indices.include?(i)
    
    if object.to_q.is_a?(q_class)
      @arg_indices.push(i)
      indices.push(i)
      true
    end
  end
end

#args?(method_args_hash) ⇒ Boolean

Stores the queue indices of the arguments required by the given method_args_hash in @arg_indices, then returns true. If the queue does not contain sufficient objects to fulfill the method argument requirements, returns false.

Returns:

  • (Boolean)


59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/q-language/environment.rb', line 59

def args? (method_args_hash)
  left_indices = []
  splat_indices = []
  right_indices = []
  queue = @scope[:queue]
  
  return false unless method_args_hash[:reqs_left].all? do |q_class|
    queue.each_with_index.any? &arg_search_block(q_class, left_indices)
  end
  
  return false unless method_args_hash[:reqs_right].all? do |q_class|
    queue.each_with_index.reverse_each.any? &arg_search_block(q_class, right_indices)
  end
  
  if q_class = method_args_hash[:splat]
    queue.each_with_index &arg_search_block(q_class, splat_indices)
  end
  
  @arg_indices = left_indices + splat_indices + right_indices
  true
end

#block_arg(block_node) ⇒ Object

Returns a Proc object to be used when calling QObject methods with required block arguments. The returned Proc uses two optional parameters: the first is a single argument object (default is nil) that will be associated with the last unassigned variable name appearing before the block in the script; the second is the Q_Environment in which to evaluate the block Node embedded in the Proc object (default is the Q_Environment that created the Proc).



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/q-language/environment.rb', line 88

def block_arg (block_node)
  return unless block_node
  
  parameter_name = @scope[:unassigned_variables].pop
  
  lambda do |parameter_object = nil, env = self|
    env.instance_eval do
      if has_old_value = @variables.has_key?(parameter_name)
        old_value = @variables[parameter_name]
      end
      
      set(parameter_name, parameter_object)
      
      b, j = @scope[:break], @scope[:jump]
      
      @scope = { queue: [], method_stack: [], unassigned_variables: [], prev: @scope }
      
      return_value = evaluate(block_node)
      b = @scope[:break] || b
      j = @scope[:jump] || j
      
      @scope = @scope[:prev]
      
      @scope[:break] = b
      @scope[:jump] = j
      
      has_old_value ? set(parameter_name, old_value) : unset(parameter_name)
      
      return_value
    end
  end
end

#break!Object

Sets a jump flag in the current scope and a break flag in the previous scope.



130
131
132
133
# File 'lib/q-language/environment.rb', line 130

def break!
  jump!
  @scope[:prev][:break] = true
end

#break?Boolean

Returns true if a break flag is set in the current scope, nil otherwise.

Returns:

  • (Boolean)


123
124
125
# File 'lib/q-language/environment.rb', line 123

def break?
  @scope[:break]
end

#evaluate(node) ⇒ Object

Evaluates the Node. For a literal Node, calls queue_push with the literal object. For a method Node, calls method_push with the method name. For a variable node, calls variable_push with the variable name. For a block Node, first tries to run any pending method that would succeed with a block argument; if no method succeeds, adds a new scope level and evaluates each Node within the block in that scope. Returns the result of calling queue_push with either the method result or with the the frontmost object from the nested scope’s queue.

Raises:



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/q-language/environment.rb', line 144

def evaluate (node)
  raise TooManyNodes if @nodes_evaluated[:total] >= @max_nodes
  
  @nodes_evaluated[node.node_type] += 1
  @nodes_evaluated[:total] += 1
  
  case node.node_type
    when :literal then queue_push(node.value)
    when :method then method_push(node.value)
    when :variable then variable_push(node.value)
    when :block
      if method?(node)
        return queue_push(@method_results.pop)
      end
      
      @scope = { queue: [], method_stack: [], unassigned_variables: [], prev: @scope }
      
      node.value.each do |node|
        evaluate node
        break if @scope[:jump]
      end
      
      return_value = @scope[:queue].shift
      
      @scope[:method_stack].each {|name| @failed_methods[name] += 1 }
      @scope[:queue].each {|obj| @discarded_objects[obj.class] += 1 }
      @scope = @scope[:prev]
      
      return queue_push(return_value)
  end
end

#frame(object, &block) ⇒ Object

Returns the result of calling the given block with the given object as the value of self.



179
180
181
182
183
184
# File 'lib/q-language/environment.rb', line 179

def frame (object, &block)
  @frame_stack.push(object)
  result = yield
  @frame_stack.pop
  result
end

#get(name) ⇒ Object

• User method Returns the object associated with the given variable name, or nil if there is no such object.



190
191
192
# File 'lib/q-language/environment.rb', line 190

def get (name)
  @variables[name]
end

#jump!Object

Sets a jump flag in the current scope.



196
197
198
# File 'lib/q-language/environment.rb', line 196

def jump!
  @scope[:jump] = true
end

#last_assigned_variableObject

Returns the name of the last variable used in the current scope that was associated with an object, or nil if no such variable exists.



203
204
205
# File 'lib/q-language/environment.rb', line 203

def last_assigned_variable
  @scope[:last_assigned_variable]
end

#method?(block_node = nil) ⇒ Boolean

Pushes the result of calling the top method in the pending methods stack using the combination of sufficient receiver and arguments found frontmost in the queue into @method_results, then returns true. If no receiver or insufficient arguments are found in the queue, returns false. If a block_node argument is given, attempts to call QObject methods requiring a block argument.

Returns:

  • (Boolean)


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
# File 'lib/q-language/environment.rb', line 214

def method? (block_node = nil)
  return false unless name = @scope[:method_stack].last
  
  q_receiver = nil
  potential_receivers = @scope[:queue] + @implicit + [QImplicit.new(@scope[:queue])]
  
  potential_receivers.each_with_index do |potential_receiver, receiver_i|
    @receiver_index = receiver_i
    q_potential_receiver = potential_receiver.to_q
    
    next unless q_potential_receiver.respond_to?(name)
    
    method_args_hash = q_potential_receiver.method(name).owner::MethodArguments[name]
    
    next if method_args_hash[:block] && !block_node
    
    @arg_indices = []
    
    if args?(method_args_hash)
      q_receiver = q_potential_receiver
      break
    end
  end
  
  return false unless q_receiver
  
  @method_results << q_send(q_receiver, block_node)
  
  true
end

#method_popObject

Pops the top method name from the pending method stack, and increments that method’s success count by 1.



248
249
250
251
252
# File 'lib/q-language/environment.rb', line 248

def method_pop
  name = @scope[:method_stack].pop
  @successful_methods[name] += 1
  name
end

#method_push(name) ⇒ Object

If the given method succeeds, calls queue_push with the result; otherwise, adds the given method name to the top of the pending method stack.



257
258
259
260
# File 'lib/q-language/environment.rb', line 257

def method_push (name)
  @scope[:method_stack].push(name)
  queue_push(@method_results.pop) if method?
end

#objectObject

• User method Returns the outermost frame object for this Q_Environment.



265
266
267
# File 'lib/q-language/environment.rb', line 265

def object
  @frame_stack.first
end

#q_send(q_receiver, block_node) ⇒ Object

Returns the sanitized result of calling the current method with the given q_receiver, the given block_node as its block arg, and the objects in the queue at the indices currently stored in @arg_indices as its arguments.



273
274
275
276
277
278
279
280
# File 'lib/q-language/environment.rb', line 273

def q_send (q_receiver, block_node)
  arg_objects = @arg_indices.map {|i| @scope[:queue][i] }
  indices = @arg_indices + [@receiver_index]
  @scope[:queue].reject!.each_with_index {|x,i| indices.include? i }
  
  q_receiver.instance_variable_set(:@__environment__, self)
  sanitize(q_receiver.__send__(method_pop, *arg_objects, &block_arg(block_node)))
end

#queue_push(object) ⇒ Object

Adds the given object to the end of the queue. Associates all unassigned variables with the object, then tests whether any pending methods succeed with the new object in the queue. If so, calls queue_push again with the result of the successful method; otherwise, returns the given object.



287
288
289
290
291
292
# File 'lib/q-language/environment.rb', line 287

def queue_push (object)
  @scope[:queue] << object
  @scope[:unassigned_variables].each {|v| set(v, object) }.clear
  
  method? ? queue_push(@method_results.pop) : object
end

#replicateObject

Returns a new Q_Environment referencing the same variables, object, and implicit receivers as this Q_Environment, but with new runtime statistics, for use in executing methods outside the context of the original Q_Device.



298
299
300
301
# File 'lib/q-language/environment.rb', line 298

def replicate
  options = [@max_nodes, @max_method_nodes, @max_array_length, @max_hash_length, @max_string_length]
  Q_Environment.new(@variables, *@implicit, @object, options)
end

#return_valueObject

• User method Returns the return value of the outermost block.



306
307
308
# File 'lib/q-language/environment.rb', line 306

def return_value
  @scope[:queue].first
end

#sanitize(obj) ⇒ Object

If the given object is an Array, Hash, or String, returns the object shortened to the maximum length; otherwise, returns the object unchanged.



313
314
315
316
317
318
319
320
321
# File 'lib/q-language/environment.rb', line 313

def sanitize (obj)
  case obj
    when Array then obj.slice!(0...max_array_length) if obj.length > @max_array_length
    when Hash then obj.pop while obj.length > @max_hash_length
    when String then obj.slice!(0...max_string_length) if obj.length > @max_string_length
  end
  
  obj
end

#selfObject

Returns the value of self for the current frame.



325
326
327
# File 'lib/q-language/environment.rb', line 325

def self
  @frame_stack.last
end

#set(name, object) ⇒ Object

• User method Associates the given variable name with the given object, then returns the object.



333
334
335
336
# File 'lib/q-language/environment.rb', line 333

def set (name, object)
  return unless name
  @variables[name] = object
end

#unset(name) ⇒ Object

Unsets the named variable, then returns the object previously associated with the variable.



341
342
343
# File 'lib/q-language/environment.rb', line 341

def unset (name)
  @variables.delete(name)
end

#variable_push(variable) ⇒ Object

If there is an object already associated with the given variable name, marks that variable name as the last assigned variable, and then calls queue_push with that object. Otherwise, adds the variable name to the list of unassigned variables.



350
351
352
353
354
355
356
357
# File 'lib/q-language/environment.rb', line 350

def variable_push (variable)
  if object = get(variable)
    @scope[:last_assigned_variable] = variable
    queue_push(object)
  else
    @scope[:unassigned_variables].push(variable)
  end
end