Create common FA/State/Transition classes across NFA/DFA

This commit is contained in:
Josh Holtrop 2021-06-06 09:41:23 -04:00
parent 1228a76c55
commit ed3f599e25
6 changed files with 144 additions and 164 deletions

View File

@ -4,6 +4,9 @@ require_relative "imbecile/cli"
require_relative "imbecile/code_point_range"
require_relative "imbecile/grammar"
require_relative "imbecile/regex"
require_relative "imbecile/regex/fa"
require_relative "imbecile/regex/fa/state"
require_relative "imbecile/regex/fa/state/transition"
require_relative "imbecile/regex/dfa"
require_relative "imbecile/regex/nfa"
require_relative "imbecile/regex/unit"

View File

@ -1,39 +1,10 @@
module Imbecile
class Regex
class DFA
class State
attr_reader :id
class Transition
attr_reader :code_point_range
attr_reader :destination
def initialize(code_point_range, destination)
@code_point_range = code_point_range
@destination = destination
end
end
attr_accessor :accepts
attr_reader :transitions
def initialize(id)
@id = id
@transitions = []
end
def add_transition(code_point_range, destination)
@transitions << Transition.new(code_point_range, destination)
end
end
class DFA < FA
def initialize(nfas)
super()
start_nfa = NFA.new
nfas.each do |nfa|
start_nfa.start_state.add_transition(nil, nfa.start_state)
@ -48,30 +19,7 @@ module Imbecile
@to_process.delete(state_set)
process_nfa_state_set(state_set)
end
end
def to_s
chr = lambda do |value|
if value < 32 || value > 127
"{#{value}}"
else
value.chr
end
end
rv = ""
@states.each_with_index do |state, state_id|
accepts_s = state.accepts ? " (#{state.accepts})" : ""
rv += "#{state_id}#{accepts_s}:\n"
state.transitions.each do |transition|
range_s = chr[transition.code_point_range.first]
if transition.code_point_range.size > 1
range_s += "-" + chr[transition.code_point_range.last]
end
accepts_s = transition.destination.accepts ? " (#{transition.destination.accepts})" : ""
rv += " #{range_s} => #{transition.destination.id}#{accepts_s}\n"
end
end
rv
@start_state = @states[0]
end
private
@ -80,7 +28,7 @@ module Imbecile
unless @nfa_state_sets.include?(nfa_state_set)
state_id = @states.size
@nfa_state_sets[nfa_state_set] = state_id
@states << State.new(state_id)
@states << State.new
@to_process << nfa_state_set
end
end

56
lib/imbecile/regex/fa.rb Normal file
View File

@ -0,0 +1,56 @@
module Imbecile
class Regex
class FA
attr_reader :start_state
def initialize
@start_state = State.new
end
def to_s
chr = lambda do |value|
if value < 32 || value > 127
"{#{value}}"
else
value.chr
end
end
rv = ""
states = {start_state => 0}
to_visit = [start_state]
state_id = lambda do |state|
unless states.include?(state)
states[state] = states.values.max + 1
to_visit << state
end
states[state]
end
visit = lambda do |state|
accepts_s = state.accepts ? " *" : ""
rv += "#{state_id[state]}#{accepts_s}:\n"
state.transitions.each do |transition|
if transition.nil?
range_s = "nil"
else
range_s = chr[transition.code_point_range.first]
if transition.code_point_range.size > 1
range_s += "-" + chr[transition.code_point_range.last]
end
end
accepts_s = transition.destination.accepts ? " *" : ""
rv += " #{range_s} => #{state_id[transition.destination]}#{accepts_s}\n"
end
end
while to_visit.size > 0
visit[to_visit[0]]
to_visit.slice!(0)
end
rv
end
end
end
end

View File

@ -0,0 +1,53 @@
module Imbecile
class Regex
class FA
class State
attr_accessor :accepts
attr_reader :transitions
def initialize
@transitions = []
end
def add_transition(code_point_range, destination)
@transitions << Transition.new(code_point_range, destination)
end
# Determine the set of states that can be reached by nil transitions.
# from this state.
#
# @return [Set<NFA::State>]
# Set of states.
def nil_transition_states
states = Set[self]
analyze_state = lambda do |state|
state.nil_transitions.each do |transition|
unless states.include?(transition.destination)
states << transition.destination
analyze_state[transition.destination]
end
end
end
analyze_state[self]
states
end
def nil_transitions
@transitions.select do |transition|
transition.nil?
end
end
def cp_transitions
@transitions.reject do |transition|
transition.nil?
end
end
end
end
end
end

View File

@ -0,0 +1,25 @@
module Imbecile
class Regex
class FA
class State
class Transition
attr_reader :code_point_range
attr_reader :destination
def initialize(code_point_range, destination)
@code_point_range = code_point_range
@destination = destination
end
def nil?
@code_point_range.nil?
end
end
end
end
end
end

View File

@ -1,120 +1,15 @@
module Imbecile
class Regex
class NFA
class NFA < FA
class State
class Transition
attr_reader :code_point_range
attr_reader :destination
def initialize(code_point_range, destination)
@code_point_range = code_point_range
@destination = destination
end
def nil?
@code_point_range.nil?
end
end
attr_accessor :accepts
attr_reader :transitions
def initialize
@transitions = []
end
def add_transition(code_point_range, destination)
@transitions << Transition.new(code_point_range, destination)
end
# Determine the set of states that can be reached by nil transitions.
# from this state.
#
# @return [Set<NFA::State>]
# Set of states.
def nil_transition_states
states = Set[self]
analyze_state = lambda do |state|
state.nil_transitions.each do |transition|
unless states.include?(transition.destination)
states << transition.destination
analyze_state[transition.destination]
end
end
end
analyze_state[self]
states
end
def nil_transitions
@transitions.select do |transition|
transition.nil?
end
end
def cp_transitions
@transitions.reject do |transition|
transition.nil?
end
end
end
attr_accessor :start_state
attr_accessor :end_state
attr_reader :end_state
def initialize
@start_state = State.new
super()
@end_state = State.new
end
def to_s
chr = lambda do |value|
if value < 32 || value > 127
"{#{value}}"
else
value.chr
end
end
rv = ""
states = {start_state => 0}
to_visit = [start_state]
state_id = lambda do |state|
unless states.include?(state)
states[state] = states.values.max + 1
to_visit << state
end
states[state]
end
visit = lambda do |state|
accepts_s = state.accepts ? " *" : ""
rv += "#{state_id[state]}#{accepts_s}:\n"
state.transitions.each do |transition|
if transition.nil?
range_s = "nil"
else
range_s = chr[transition.code_point_range.first]
if transition.code_point_range.size > 1
range_s += "-" + chr[transition.code_point_range.last]
end
end
accepts_s = transition.destination.accepts ? " *" : ""
rv += " #{range_s} => #{state_id[transition.destination]}#{accepts_s}\n"
end
end
while to_visit.size > 0
visit[to_visit[0]]
to_visit.slice!(0)
end
rv
end
class << self
def empty