#!/usr/bin/ruby -w class FiniteAutomaton @@nextID = 0 # shared across all states attr_reader:state, :start, :final, :alphabet, :transition #--------------------------------------------------------------- # Constructor for the FA def initialize @start = nil # start state @state = { } # all states @final = { } # final states @transition = { } # transitions @alphabet = [ ] # symbols on transitions end #--------------------------------------------------------------- # Return number of states def num_states @state.size end #--------------------------------------------------------------- # Creates a new state def new_state newID = @@nextID @@nextID += 1 @state[newID] = true @transition[newID] = {} return newID end #--------------------------------------------------------------- # Creates a new state def add_state(v) unless has_state?(v) @state[v] = true @transition[v] = {} end end #--------------------------------------------------------------- # Returns true if the state exists def has_state?(v) @state[v] end #--------------------------------------------------------------- # Set (or reset) the start state def set_start(v) add_state(v) @start = v end #--------------------------------------------------------------- # Set (or reset) a final state def set_final(v, final = true) add_state(v) if final @final[v] = true else @final.delete(v) end end #--------------------------------------------------------------- # Returns true if the state is final def is_final?(v) @final[v] end #--------------------------------------------------------------- # Creates a new transition from v1 to v2 with symbol x # Any previous transition from v1 with symbol x is removed def add_transition(v1, v2, x) add_state(v1) add_state(v2) @transition[v1][x] = v2 end #--------------------------------------------------------------- # Get the destination state from v1 with symbol x # Returns nil if non-existent def get_transition(v1,x) if has_state?(v1) @transition[v1][x] else nil end end #--------------------------------------------------------------- # Returns true if the dfa accepts the given string def accept?(s, current = @start) if s == "" is_final?(current) else dest = get_transition(current,s[0,1]) if dest == nil false else accept?(s[1..-1], dest) end end end #--------------------------------------------------------------- # Returns all states reachable from starting states # using only epsilon transitions def eClosure(ss) end #--------------------------------------------------------------- # Prints FA def prettyPrint print "% Start " puts @start # Final states in sorted order print "% Final {" @final.keys.sort.each { |x| print " #{x}" } puts " }" # States in sorted order print "% States {" @state.keys.sort.each { |x| print " #{x}" } puts " }" # Alphabet in alphabetical order print "% Alphabet {" @alphabet.each { |x| print " #{x}" } puts " }" # Transitions in lexicographic order puts "% Transitions {" @transition.keys.sort.each { |v1| @transition[v1].keys.sort.each { |x| v2 = get_transition(v1,x) puts "% (#{v1} #{x} #{v2})" } } puts "% }" end #--------------------------------------------------------------- # accepts just symbol ("" = epsilon) def symbol! sym initialize s0 = new_state s1 = new_state set_start(s0) set_final(s1, true) add_transition(s0, s1, sym) end #--------------------------------------------------------------- # accept strings accepted by self, followed by strings accepted by newFA def concat! newFA end #--------------------------------------------------------------- # accept strings accepted by either self or newFA def union! newFA end #--------------------------------------------------------------- # accept any sequence of 0 or more strings accepted by self def closure! end #--------------------------------------------------------------- # returns DFA that accepts only strings accepted by self def toDFA dfa = FiniteAutomaton.new return dfa end #--------------------------------------------------------------- # accept only strings accepted by self, but as minimal DFA def minimize! end #--------------------------------------------------------------- # return all strings accepted by FA with length <= strLen def genStr strLen resultStrs = [ ] testStrings = [ ] testStrings[0] = [] testStrings[0].push "" 1.upto(strLen.to_i) { |x| testStrings[x] = [] testStrings[x-1].each { |s| @alphabet.each { |c| testStrings[x].push s+c } } } testStrings.flatten.each { |s| resultStrs.push s if accept? s } result = "" resultStrs.each { |x| result.concat '"'+x+'" ' } return result end end #--------------------------------------------------------------- # read standard input and interpret as a stack machine def interpreter dfaStack = [ ] loop do line = gets words = line.scan(/\S+/) words.each{ |word| case word when /DONE/ return when /SIZE/ f = dfaStack.last puts f.num_states when /PRINT/ f = dfaStack.last f.prettyPrint when /DFA/ f = dfaStack.pop f2 = f.toDFA dfaStack.push f2 when /MINIMIZE/ f = dfaStack.pop f.minimize! dfaStack.push f when /GENSTR([0-9]+)/ f = dfaStack.last puts f.genStr($1) when /"([a-z]*)"/ f = dfaStack.last str = $1 if f.accept?(str) puts "Accept #{str}" else puts "Reject #{str}" end when /([a-zE])/ f = FiniteAutomaton.new sym = $1 sym="" if $1=="E" f.symbol!(sym) dfaStack.push f when /\*/ f = dfaStack.pop f.closure! dfaStack.push f when /\|/ f1 = dfaStack.pop f2 = dfaStack.pop f2.union!(f1) dfaStack.push f2 when /\./ f1 = dfaStack.pop f2 = dfaStack.pop f2.concat!(f1) dfaStack.push f2 else puts "Ignoring #{word}" end } end end #--------------------------------------------------------------- # main( ) if false # just debugging messages f = FiniteAutomaton.new f.set_start(1) f.set_final(2) f.set_final(3) f.add_transition(1,2,"a") # need to keep this for NFA f.add_transition(1,3,"a") f.prettyPrint end interpreter # type "DONE" to exit