diff --git a/lib/imbecile.rb b/lib/imbecile.rb index 1473025..737e779 100644 --- a/lib/imbecile.rb +++ b/lib/imbecile.rb @@ -2,17 +2,17 @@ require "erb" require "set" require_relative "imbecile/cli" require_relative "imbecile/code_point_range" +require_relative "imbecile/fa" +require_relative "imbecile/fa/state" +require_relative "imbecile/fa/state/transition" require_relative "imbecile/generator" require_relative "imbecile/grammar" require_relative "imbecile/grammar/rule" require_relative "imbecile/grammar/token" +require_relative "imbecile/lexer_dfa" 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/nfa" require_relative "imbecile/regex/unit" -require_relative "imbecile/lexer_dfa" require_relative "imbecile/version" module Imbecile diff --git a/lib/imbecile/fa.rb b/lib/imbecile/fa.rb new file mode 100644 index 0000000..489afa7 --- /dev/null +++ b/lib/imbecile/fa.rb @@ -0,0 +1,90 @@ +module Imbecile + + 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 = enumerate + states.each do |state, id| + accepts_s = state.accepts ? " #{state.accepts}" : "" + rv += "#{id}#{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 ? " #{transition.destination.accepts}" : "" + rv += " #{range_s} => #{states[transition.destination]}#{accepts_s}\n" + end + end + rv + end + + def enumerate + @_enumerated ||= + begin + id = 0 + states = {} + visit = lambda do |state| + unless states.include?(state) + states[state] = id + id += 1 + state.transitions.each do |transition| + visit[transition.destination] + end + end + end + visit[@start_state] + states + end + end + + def build_tables + transition_table = [] + state_table = [] + states = enumerate + states.each do |state, id| + accepts = + if state.accepts.nil? + TOKEN_NONE + elsif state.accepts.name + state.accepts.id + else + TOKEN_DROP + end + state_table << { + transition_table_index: transition_table.size, + n_transitions: state.transitions.size, + accepts: accepts, + } + state.transitions.each do |transition| + transition_table << { + first: transition.code_point_range.first, + last: transition.code_point_range.last, + destination: states[transition.destination], + } + end + end + [transition_table, state_table] + end + + end + +end diff --git a/lib/imbecile/fa/state.rb b/lib/imbecile/fa/state.rb new file mode 100644 index 0000000..1373cbc --- /dev/null +++ b/lib/imbecile/fa/state.rb @@ -0,0 +1,51 @@ +module Imbecile + 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] + # 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 diff --git a/lib/imbecile/fa/state/transition.rb b/lib/imbecile/fa/state/transition.rb new file mode 100644 index 0000000..bd07671 --- /dev/null +++ b/lib/imbecile/fa/state/transition.rb @@ -0,0 +1,23 @@ +module Imbecile + 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 diff --git a/lib/imbecile/lexer_dfa.rb b/lib/imbecile/lexer_dfa.rb index 8ec7965..63570d8 100644 --- a/lib/imbecile/lexer_dfa.rb +++ b/lib/imbecile/lexer_dfa.rb @@ -1,6 +1,6 @@ module Imbecile - class LexerDFA < Regex::FA + class LexerDFA < FA def initialize(tokens) super() diff --git a/lib/imbecile/regex/fa.rb b/lib/imbecile/regex/fa.rb deleted file mode 100644 index 076d715..0000000 --- a/lib/imbecile/regex/fa.rb +++ /dev/null @@ -1,92 +0,0 @@ -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 = enumerate - states.each do |state, id| - accepts_s = state.accepts ? " #{state.accepts}" : "" - rv += "#{id}#{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 ? " #{transition.destination.accepts}" : "" - rv += " #{range_s} => #{states[transition.destination]}#{accepts_s}\n" - end - end - rv - end - - def enumerate - @_enumerated ||= - begin - id = 0 - states = {} - visit = lambda do |state| - unless states.include?(state) - states[state] = id - id += 1 - state.transitions.each do |transition| - visit[transition.destination] - end - end - end - visit[@start_state] - states - end - end - - def build_tables - transition_table = [] - state_table = [] - states = enumerate - states.each do |state, id| - accepts = - if state.accepts.nil? - TOKEN_NONE - elsif state.accepts.name - state.accepts.id - else - TOKEN_DROP - end - state_table << { - transition_table_index: transition_table.size, - n_transitions: state.transitions.size, - accepts: accepts, - } - state.transitions.each do |transition| - transition_table << { - first: transition.code_point_range.first, - last: transition.code_point_range.last, - destination: states[transition.destination], - } - end - end - [transition_table, state_table] - end - - end - - end -end diff --git a/lib/imbecile/regex/fa/state.rb b/lib/imbecile/regex/fa/state.rb deleted file mode 100644 index ee4fab9..0000000 --- a/lib/imbecile/regex/fa/state.rb +++ /dev/null @@ -1,53 +0,0 @@ -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] - # 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 diff --git a/lib/imbecile/regex/fa/state/transition.rb b/lib/imbecile/regex/fa/state/transition.rb deleted file mode 100644 index 5bd0efb..0000000 --- a/lib/imbecile/regex/fa/state/transition.rb +++ /dev/null @@ -1,25 +0,0 @@ -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