lib/interpreter/interpreter.rb in nudge-0.2.3 vs lib/interpreter/interpreter.rb in nudge-0.2.4

- old
+ new

@@ -1,29 +1,32 @@ #encoding: utf-8 module Nudge # The Interpreter class executes the Push3-like language loop: - # 1. Pop the top item off the <b>:exec</b> Stack + # 1. Pop the top item off the <tt>:exec</tt> Stack # 2. If it is a(n)... # * ... InstructionPoint, execute that instruction's go() method; # * ... ValuePoint, push its value to the Stack it names; - # * ... ReferencePoint (Variable or Name), ... + # * ... 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 <b>:exec</b> 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> class Interpreter attr_accessor :program, :step_limit, :steps attr_accessor :stacks, :instructions_library, :variables, :names, :types attr_accessor :last_name, :evaluate_references attr_accessor :sensors attr_accessor :code_char_limit attr_accessor :start_time, :time_limit - # A program to be interpreted can be passed in as an optional parameter def initialize(program = nil, params = {}) initialProgram = program @program = initialProgram @types = params[:types] || NudgeType.all_types @step_limit = params[:step_limit] || 3000 @@ -53,11 +56,14 @@ # * clears all the Stacks (including the <b>:exec</b> 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 @step counter + # * resets the name assignments + # * resets the start_time (intentional redundancy) + # * resets a number of state variables def reset(program=nil) @program = program self.clear_stacks self.reset_names self.reset_sensors @@ -68,72 +74,85 @@ @start_time = Time.now @evaluate_references = true end + + # Deletes all items from all stacks def clear_stacks @stacks = Hash.new {|hash, key| hash[key] = Stack.new(key) } end + # Returns the count of items in a given stack def depth(stackname) @stacks[stackname].depth end + # Returns a link to the top item in a given stack (not its value) def peek(stackname) @stacks[stackname].peek end + # Returns a link to the value of the top item in a given stack def peek_value(stackname) item = @stacks[stackname].peek item.nil? ? nil : item.value end + # Removes the top item from a given stack and returns it def pop(stackname) @stacks[stackname].pop end + # Removes the top item from a given stack and returns its value def pop_value(stackname) item = @stacks[stackname].pop item.nil? ? nil : item.value end + # Adds a new ValuePoint item, with the given value, to the named stack def push(stackname, value="") @stacks[stackname].push(ValuePoint.new(stackname, value)) end # Checks to see if either stopping condition applies: # 1. Is the <b>:exec</b> 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? def notDone? @stacks[:exec].depth > 0 && @steps < @step_limit && (Time.now-@start_time)<@time_limit end # Execute one cycle of the Push3 interpreter rule: # 1. check termination conditions with self.notDone()? - # 2. pop one item from <b>:exec</b> - # 3. call its go() method + # 2. pop one item from <tt>:exec</tt> + # 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. def step if notDone? nextPoint = @stacks[:exec].pop nextPoint.go(self) @steps += 1 end end + # Returns an Array containing the class names of all <i>active</i> instructions def instructions @instructions_library.keys end @@ -145,118 +164,151 @@ end fire_all_sensors end + # 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 def lookup(name) @variables[name] || @names[name] end + # returns an Array of all strings defined as variables or names def references @names.merge(@variables).keys end + # 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. def enable(item) if item.superclass == Instruction @instructions_library[item] = item.new(self) elsif item.include? NudgeType @types |= [item] end end + # Convenience method that checks to see whether an Instruction or NudgeType class is currently + # in the active state. Returns a boolean. def active?(item) if item.superclass == Instruction @instructions_library.include?(item) elsif item.include? NudgeType @types.include?(item) end end + # Given a string and a ProgramPoint, binds a variable with that name to that ProgramPoint def bind_variable(name, value) raise(ArgumentError, "Variables can only be bound to ProgramPoints") unless value.kind_of?(ProgramPoint) @variables[name] = value end + # Given a string and a ProgramPoint, binds a name with that name to that ProgramPoint def bind_name(name, value) raise(ArgumentError, "Names can only be bound to ProgramPoints") unless value.kind_of?(ProgramPoint) @names[name] = value end + # generates an arbitrary string for naming new local variables, by incrememnting + # from the starting point "aaa001" def next_name @last_name = @last_name.next end + # removes the named global variable from the Hash that defines them def unbind_variable(name) @variables.delete(name) end + # removes the named local variable from the Hash that defines them def unbind_name(name) @names.delete(name) end + # removes all global variable definitions def reset_variables @variables = Hash.new end + # removes all local variable definitions def reset_names @names = Hash.new end + # activates every Instruction subclass defined in any library def enable_all_instructions Instruction.all_instructions.each do |i| @instructions_library[i] = i.new(self) end end + # activates every NudgeType subclass defined in any library def enable_all_types @types = NudgeType.all_types end + # 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. def disable(item) if item.superclass == Instruction @instructions_library.delete(item) elsif item.include? NudgeType @types.delete(item) end end + # Completely empties the set of active Instructions. The interpreter will recognize InstructionPoints, + # but will not invoke their #go methods when it does. def disable_all_instructions @instructions_library = Hash.new end + # 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. def disable_all_types @types = [] end + # 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. def register_sensor(name, &block) raise(ArgumentError, "Sensor name #{name} is not a string") unless name.kind_of?(String) @sensors[name] = block end + # Delete all sensors. def reset_sensors @sensors = Hash.new end + # Iterates through the Interpreter#sensors hash, #calling each one and passing in the current state + # of the Interpreter as an argument def fire_all_sensors @sensors.inject({}) do |result, (key, value)| result[key] = @sensors[key].call(self) result end \ No newline at end of file