module Stamina class Automaton # # Automaton state. # class State include Stamina::Markable attr_reader :automaton, :index # # Creates a state. # # Arguments: # - automaton: parent automaton of the state. # - index: index of the state in the state list. # - data: user data attached to this state. # def initialize(automaton, index, data) @automaton = automaton @index = index @data = data.dup @out_edges = [] @in_edges = [] @epsilon_closure = nil end ### public read-only section ############################################### public # Returns true if this state is an initial state, false otherwise. def initial? !!@data[:initial] end # Sets this state as an initial state. def initial!(value = true) @data[:initial] = value end # Returns true if this state is an accepting state, false otherwise. def accepting? !!@data[:accepting] end # Sets this state as an accepting state. def accepting!(value = true) @data[:accepting] = value end # Returns true if this state is an error state, false otherwise. def error? !!@data[:error] end # Sets this state as an error state. def error!(value = true) @data[:error] = value end # Returns true if this state is deterministic, false otherwise. def deterministic? outs = out_symbols (outs.size==@out_edges.size) and not(outs.include?(nil)) end # Checks if this state is a sink state or not. Sink states are defined as # non accepting states having no outgoing transition or only loop # transitions. def sink? !accepting? && out_edges.all?{|e| e.target==self} end # Returns an array containing all incoming edges of the state. Edges are # sorted if _sorted_ is set to true. If two incoming edges have same symbol # no order is guaranteed between them. # # Returned array may be modified. def in_edges(sorted=false) sorted ? @in_edges.sort : @in_edges.dup end # Returns an array containing all outgoing edges of the state. Edges are # sorted if _sorted_ is set to true. If two outgoing edges have same symbol # no order is guaranteed between them. # # Returned array may be modified. def out_edges(sorted=false) sorted ? @out_edges.sort : @out_edges.dup end # Returns an array with the different symbols appearing on incoming edges. # Returned array does not contain duplicates. Symbols are sorted in the # array if _sorted_ is set to true. # # Returned array may be modified. def in_symbols(sorted=false) symbols = @in_edges.collect{|e| e.symbol}.uniq return sorted ? (symbols.sort &automaton.symbols_comparator) : symbols end # Returns an array with the different symbols appearing on outgoing edges. # Returned array does not contain duplicates. Symbols are sorted in the # array if _sorted_ is set to true. # # Returned array may be modified. def out_symbols(sorted=false) symbols = @out_edges.collect{|e| e.symbol}.uniq return sorted ? (symbols.sort &automaton.symbols_comparator) : symbols end # Returns an array with adjacent states (in or out edge). # # Returned array may be modified. def adjacent_states() (in_adjacent_states+out_adjacent_states).uniq end # Returns an array with adjacent states along an incoming edge (without # duplicates). # # Returned array may be modified. def in_adjacent_states() (@in_edges.collect {|e| e.source}).uniq end # Returns an array with adjacent states along an outgoing edge (whithout # duplicates). # # Returned array may be modified. def out_adjacent_states() (@out_edges.collect {|e| e.target}).uniq end # Returns reachable states from this one with an input _symbol_. Returned # array does not contain duplicates and may be modified. This method if not # epsilon symbol aware. def step(symbol) @out_edges.select{|e| e.symbol==symbol}.collect{|e| e.target} end # Returns the state reached from this one with an input _symbol_, or nil if # no such state. This method is not epsilon symbol aware. Moreover it is # expected to be used on deterministic states only. If the state is not # deterministic, the method returns one reachable state if such a state # exists; which one is returned must be considered non deterministic. def dfa_step(symbol) edge = @out_edges.find{|e| e.symbol==symbol} edge ? edge.target : nil end # Computes the epsilon closure of this state. Epsilon closure is the set of # all states reached from this one with a eps* input (sequence of # zero or more epsilon symbols). The current state is always contained in # the epsilon closure. Returns an unsorted array without duplicates; this # array may not be modified. def epsilon_closure() @epsilon_closure ||= compute_epsilon_closure(Set.new).to_a.freeze end # Internal implementation of epsilon_closure. _result_ is expected to be # a Set instance, is modified and is the returned value. def compute_epsilon_closure(result) result << self step(nil).each do |t| t.compute_epsilon_closure(result) unless result.include?(t) end raise if result.nil? return result end # Computes an array representing the set of states that can be reached from # this state with a given input _symbol_. Returned array does not contain # duplicates and may be modified. No particular ordering of states in the # array is guaranteed. # # This method is epsilon symbol aware (represented with nil) on non # deterministic automata, meaning that it actually computes the set of # reachable states through strings respecting the eps* symbol eps* # regular expression, where eps is the epsilon symbol. def delta(symbol) if automaton.deterministic? target = dfa_delta(symbol) target.nil? ? [] : [target] else at_epsilon = epsilon_closure at_espilon_then_symbol = at_epsilon.map{|s| s.step(symbol)}.flatten.uniq at_espilon_then_symbol.map{|s| s.epsilon_closure}.flatten.uniq end end # Returns the target state that can be reached from this state with _symbol_ # input. Returns nil if no such state exists. # # This method is expected to be used on deterministic automata. Unlike delta, # it returns a State instance (or nil), not an array of states. When used on # non deterministic automata, it returns a state immediately reachable from # this state with _symbol_ input, or nil if no such state exists. This # method is not epsilon aware. def dfa_delta(symbol) return nil if symbol.nil? edge = @out_edges.find{|e| e.symbol==symbol} edge.nil? ? nil : edge.target end # Provides comparator of states, based on the index in the automaton state # list. This method returns nil unless _o_ is a State from the same # automaton than self. def <=>(o) return nil unless State===o return nil unless automaton===o.automaton return index <=> o.index end # Returns a string representation def inspect 's' << @index.to_s end # Returns a string representation def to_s 's' << @index.to_s end ### protected write section ################################################ protected # Changes the index of this state in the state list. This method is only # expected to be used by the automaton itself. def index=(i) @index=i end # # Fired by Loaded when a user data is changed. The message is forwarded to # the automaton. # def state_changed(what, description) @epsilon_closure = nil @automaton.send(:state_changed, what, description) end # Adds an incoming edge to the state. def add_incoming_edge(edge) @epsilon_closure = nil @in_edges << edge end # Adds an outgoing edge to the state. def add_outgoing_edge(edge) @epsilon_closure = nil @out_edges << edge end # Adds an incoming edge to the state. def drop_incoming_edge(edge) @epsilon_closure = nil @in_edges.delete(edge) end # Adds an outgoing edge to the state. def drop_outgoing_edge(edge) @epsilon_closure = nil @out_edges.delete(edge) end protected :compute_epsilon_closure end # class State end # class Automaton end # module Stamina