Class: Nudge::Interpreter

Inherits:
Object
  • Object
show all
Defined in:
lib/interpreter/interpreter.rb

Overview

The Interpreter class executes the Push3-like language loop:

  1. Pop the top item off the :exec Stack

  2. If it is a(n)…

    • … InstructionPoint, execute that instruction’s go() method;

    • … ValuePoint, push its value to the Stack it names;

    • … ReferencePoint (a reference to a Variable or Name), …

      * ... if it's bound to a value, push the bound value onto the <b>:exec</b> Stack;
      * ... if it's not bound, push the name itself onto the <b>:name</b> Stack;
      
    • … CodeblockPoint, push its #contents (in the same order) back onto the :exec Stack

    • … NilPoint, do nothing

This cycle repeats until one of the termination conditions is met:

* nothing more remains on the <tt>:exec/tt> stack
* the number of cycles meets or exceeds the <tt>step_limit</tt>
* the wall-clock time meets or exceeds the <tt>time_limit</tt>

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(program = nil, params = {}) ⇒ Interpreter

Returns a new instance of Interpreter.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/interpreter/interpreter.rb', line 28

def initialize(program = nil, params = {})
  initialProgram = program
  @program = initialProgram
  @types = params[:types] || NudgeType.all_types
  @step_limit = params[:step_limit] || 3000
  @time_limit = params[:time_limit] || 60.0 # seconds
  @code_char_limit = params[:code_char_limit] || 2000
  @sensors = Hash.new
  
  instructions = params[:instructions] || Instruction.all_instructions
  @instructions_library = Hash.new {|hash, key| raise InstructionPoint::InstructionNotFoundError,
    "#{key} is not an active instruction in this context"}
  instructions.each {|i| self.enable(i)}
  
  # private parts
  @names = Hash.new
  @variables = Hash.new
  @steps = 0
  @last_name = "refAAAAA"
  @evaluate_references = true
  @stacks =  Hash.new {|hash, key| hash[key] = Stack.new(key) }
  
  # set it all up here
  self.reset(initialProgram)
end

Instance Attribute Details

#code_char_limitObject

Returns the value of attribute code_char_limit.



24
25
26
# File 'lib/interpreter/interpreter.rb', line 24

def code_char_limit
  @code_char_limit
end

#evaluate_referencesObject

Returns the value of attribute evaluate_references.



22
23
24
# File 'lib/interpreter/interpreter.rb', line 22

def evaluate_references
  @evaluate_references
end

#instructions_libraryObject

Returns the value of attribute instructions_library.



21
22
23
# File 'lib/interpreter/interpreter.rb', line 21

def instructions_library
  @instructions_library
end

#last_nameObject

Returns the value of attribute last_name.



22
23
24
# File 'lib/interpreter/interpreter.rb', line 22

def last_name
  @last_name
end

#namesObject

Returns the value of attribute names.



21
22
23
# File 'lib/interpreter/interpreter.rb', line 21

def names
  @names
end

#programObject

Returns the value of attribute program.



20
21
22
# File 'lib/interpreter/interpreter.rb', line 20

def program
  @program
end

#sensorsObject

Returns the value of attribute sensors.



23
24
25
# File 'lib/interpreter/interpreter.rb', line 23

def sensors
  @sensors
end

#stacksObject

Returns the value of attribute stacks.



21
22
23
# File 'lib/interpreter/interpreter.rb', line 21

def stacks
  @stacks
end

#start_timeObject

Returns the value of attribute start_time.



25
26
27
# File 'lib/interpreter/interpreter.rb', line 25

def start_time
  @start_time
end

#step_limitObject

Returns the value of attribute step_limit.



20
21
22
# File 'lib/interpreter/interpreter.rb', line 20

def step_limit
  @step_limit
end

#stepsObject

Returns the value of attribute steps.



20
21
22
# File 'lib/interpreter/interpreter.rb', line 20

def steps
  @steps
end

#time_limitObject

Returns the value of attribute time_limit.



25
26
27
# File 'lib/interpreter/interpreter.rb', line 25

def time_limit
  @time_limit
end

#typesObject

Returns the value of attribute types.



21
22
23
# File 'lib/interpreter/interpreter.rb', line 21

def types
  @types
end

#variablesObject

Returns the value of attribute variables.



21
22
23
# File 'lib/interpreter/interpreter.rb', line 21

def variables
  @variables
end

Instance Method Details

#active?(item) ⇒ Boolean

Convenience method that checks to see whether an Instruction or NudgeType class is currently in the active state. Returns a boolean.

Returns:

  • (Boolean)


197
198
199
200
201
202
203
# File 'lib/interpreter/interpreter.rb', line 197

def active?(item)
  if item.superclass == Instruction
    @instructions_library.include?(item)
  elsif item.include? NudgeType        
    @types.include?(item)
  end
end

#bind_name(name, value) ⇒ Object

Given a string and a ProgramPoint, binds a name with that name to that ProgramPoint

Raises:

  • (ArgumentError)


215
216
217
218
219
# File 'lib/interpreter/interpreter.rb', line 215

def bind_name(name, value)
  raise(ArgumentError, "Names can only be bound to ProgramPoints") unless
    value.kind_of?(ProgramPoint)
  @names[name] = value
end

#bind_variable(name, value) ⇒ Object

Given a string and a ProgramPoint, binds a variable with that name to that ProgramPoint

Raises:

  • (ArgumentError)


207
208
209
210
211
# File 'lib/interpreter/interpreter.rb', line 207

def bind_variable(name, value)
  raise(ArgumentError, "Variables can only be bound to ProgramPoints") unless
    value.kind_of?(ProgramPoint)
  @variables[name] = value
end

#clear_stacksObject

Deletes all items from all stacks



81
82
83
# File 'lib/interpreter/interpreter.rb', line 81

def clear_stacks
  @stacks = Hash.new {|hash, key| hash[key] = Stack.new(key) }
end

#depth(stackname) ⇒ Object

Returns the count of items in a given stack



87
88
89
# File 'lib/interpreter/interpreter.rb', line 87

def depth(stackname)
  @stacks[stackname].depth
end

#disable(item) ⇒ Object

Convenience method that can be called with either an Instruction or NudgeType class as an argument. If an Instruction, that class is removed from the Interpreter’s #instruction_library. If a NudgeType, that class is removed to the list of types that can be used to generate random code.



271
272
273
274
275
276
277
# File 'lib/interpreter/interpreter.rb', line 271

def disable(item)
  if item.superclass == Instruction
    @instructions_library.delete(item)
  elsif item.include? NudgeType
    @types.delete(item)
  end
end

#disable_all_instructionsObject

Completely empties the set of active Instructions. The interpreter will recognize InstructionPoints, but will not invoke their #go methods when it does.



282
283
284
# File 'lib/interpreter/interpreter.rb', line 282

def disable_all_instructions
  @instructions_library = Hash.new
end

#disable_all_typesObject

Completely empties the set of NudgeTypes in play. ValuePoints the Interpreter encounters will still be recognized in code, and will still be pushed to the appropriate stack, but new ValuePoints (made by various code-generating methods) will not be created.



290
291
292
# File 'lib/interpreter/interpreter.rb', line 290

def disable_all_types
  @types = []
end

#enable(item) ⇒ Object

Convenience method that can be called with either an Instruction or NudgeType class as an argument. If an Instruction, that class is added to the Interpreter’s #instruction_library. If a NudgeType, that class is added to the list of types that can be used to generate random code.



186
187
188
189
190
191
192
# File 'lib/interpreter/interpreter.rb', line 186

def enable(item)
  if item.superclass == Instruction
    @instructions_library[item] = item.new(self)
  elsif item.include? NudgeType
    @types |= [item]
  end
end

#enable_all_instructionsObject

activates every Instruction subclass defined in any library



254
255
256
257
258
# File 'lib/interpreter/interpreter.rb', line 254

def enable_all_instructions
  Instruction.all_instructions.each do |i|
    @instructions_library[i] = i.new(self)
  end
end

#enable_all_typesObject

activates every NudgeType subclass defined in any library



262
263
264
# File 'lib/interpreter/interpreter.rb', line 262

def enable_all_types
  @types = NudgeType.all_types
end

#fire_all_sensorsObject

Iterates through the Interpreter#sensors hash, #calling each one and passing in the current state of the Interpreter as an argument



311
312
313
314
315
316
# File 'lib/interpreter/interpreter.rb', line 311

def fire_all_sensors
  @sensors.inject({}) do |result, (key, value)|
    result[key] = @sensors[key].call(self)
    result
  end
end

#instructionsObject

Returns an Array containing the class names of all active instructions



154
155
156
# File 'lib/interpreter/interpreter.rb', line 154

def instructions
  @instructions_library.keys
end

#lookup(name) ⇒ Object

given a string, checks the hash of defined variables, then the names (local variables), returning the bound value, or nil if it is not found



171
172
173
# File 'lib/interpreter/interpreter.rb', line 171

def lookup(name)
  @variables[name] || @names[name]
end

#next_nameObject

generates an arbitrary string for naming new local variables, by incrememnting from the starting point “aaa001”



224
225
226
# File 'lib/interpreter/interpreter.rb', line 224

def next_name
  @last_name = @last_name.next
end

#notDone?Boolean

Checks to see if either stopping condition applies:

  1. Is the :exec stack empty?

  2. Are the number of steps greater than self.step_limit?

  3. Has the total time since recorded self.start_time exceeded self.time_limit?

Returns:

  • (Boolean)


129
130
131
132
133
# File 'lib/interpreter/interpreter.rb', line 129

def notDone?
  @stacks[:exec].depth > 0 &&
  @steps < @step_limit && 
  (Time.now-@start_time)<@time_limit
end

#peek(stackname) ⇒ Object

Returns a link to the top item in a given stack (not its value)



93
94
95
# File 'lib/interpreter/interpreter.rb', line 93

def peek(stackname)
  @stacks[stackname].peek
end

#peek_value(stackname) ⇒ Object

Returns a link to the value of the top item in a given stack



99
100
101
102
# File 'lib/interpreter/interpreter.rb', line 99

def peek_value(stackname)
  item = @stacks[stackname].peek
  item.nil? ? nil : item.value
end

#pop(stackname) ⇒ Object

Removes the top item from a given stack and returns it



106
107
108
# File 'lib/interpreter/interpreter.rb', line 106

def pop(stackname)
  @stacks[stackname].pop
end

#pop_value(stackname) ⇒ Object

Removes the top item from a given stack and returns its value



112
113
114
115
# File 'lib/interpreter/interpreter.rb', line 112

def pop_value(stackname)
  item = @stacks[stackname].pop
  item.nil? ? nil : item.value
end

#push(stackname, value = "") ⇒ Object

Adds a new ValuePoint item, with the given value, to the named stack



119
120
121
# File 'lib/interpreter/interpreter.rb', line 119

def push(stackname, value="")
  @stacks[stackname].push(ValuePoint.new(stackname, value))
end

#referencesObject

returns an Array of all strings defined as variables or names



177
178
179
# File 'lib/interpreter/interpreter.rb', line 177

def references
  @names.merge(@variables).keys
end

#register_sensor(name, &block) ⇒ Object

Create a new sensor with the given name, binding the associated block argument. All sensors are called, in the order registered, when the Interpreter#run cycle terminates normally.

Raises:

  • (ArgumentError)


297
298
299
300
# File 'lib/interpreter/interpreter.rb', line 297

def register_sensor(name, &block)
  raise(ArgumentError, "Sensor name #{name} is not a string") unless name.kind_of?(String)
  @sensors[name] = block
end

#reset(program = nil) ⇒ Object

Resets the Interpreter state:

  • clears all the Stacks (including the :exec Stack)

  • loads a new program,

    * parses the program
    * if it parses, pushes it onto the <b>:exec</b> Stack
    * (and if it doesn't parse, leaves all stacks empty)
    
  • resets the @step counter

  • resets the name assignments

  • resets the start_time (intentional redundancy)

  • resets a number of state variables



65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/interpreter/interpreter.rb', line 65

def reset(program=nil)
  @program = program
  self.clear_stacks
  self.reset_names
  self.reset_sensors
  if !program.nil?
    @stacks[:exec].push(NudgeProgram.new(program).linked_code)
  end
  @steps = 0
  @start_time = Time.now
  @evaluate_references = true
end

#reset_namesObject

removes all local variable definitions



248
249
250
# File 'lib/interpreter/interpreter.rb', line 248

def reset_names
  @names = Hash.new
end

#reset_sensorsObject

Delete all sensors.



304
305
306
# File 'lib/interpreter/interpreter.rb', line 304

def reset_sensors
  @sensors = Hash.new
end

#reset_variablesObject

removes all global variable definitions



242
243
244
# File 'lib/interpreter/interpreter.rb', line 242

def reset_variables
  @variables = Hash.new
end

#runObject

invoke self.step() until a termination condition is true



160
161
162
163
164
165
166
# File 'lib/interpreter/interpreter.rb', line 160

def run
  @start_time = Time.now
  while notDone?
    self.step
  end
  fire_all_sensors
end

#stepObject

Execute one cycle of the Push3 interpreter rule:

  1. check termination conditions with self.notDone()?

  2. pop one item from :exec

  3. call that item’s #go method

  4. increment the step counter self#steps

Note that the start_time attribute is not adjusted; if called a long time after resetting, it may time out unexpectedly.



144
145
146
147
148
149
150
# File 'lib/interpreter/interpreter.rb', line 144

def step
  if notDone?
    nextPoint = @stacks[:exec].pop
    nextPoint.go(self)
    @steps += 1
  end
end

#unbind_name(name) ⇒ Object

removes the named local variable from the Hash that defines them



236
237
238
# File 'lib/interpreter/interpreter.rb', line 236

def unbind_name(name)
  @names.delete(name)
end

#unbind_variable(name) ⇒ Object

removes the named global variable from the Hash that defines them



230
231
232
# File 'lib/interpreter/interpreter.rb', line 230

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