174 lines
4.3 KiB
Ruby

module Imbecile
class Regex
class Unit
end
class SequenceUnit < Unit
attr_accessor :units
def initialize
@units = []
end
def method_missing(*args)
@units.__send__(*args)
end
def to_nfa
if @units.empty?
NFA.empty
else
@units.map do |unit|
unit.to_nfa
end.reduce do |result, nfa|
result.end_state.add_transition(nil, nfa.start_state)
result
end
end
end
end
class AlternatesUnit < Unit
attr_accessor :alternates
def initialize
@alternates = []
new_alternate!
end
def new_alternate!
@alternates << SequenceUnit.new
end
def <<(unit)
@alternates[-1] << unit
end
def last_unit
@alternates[-1][-1]
end
def replace_last!(new_unit)
@alternates[-1][-1] = new_unit
end
def to_nfa
if @alternates.size == 0
NFA.empty
elsif @alternates.size == 1
@alternates[0].to_nfa
else
nfa = NFA.new
alternate_nfas = @alternates.map do |alternate|
alternate.to_nfa
end
alternate_nfas.each do |alternate_nfa|
nfa.start_state.add_transition(nil, alternate_nfa.start_state)
alternate_nfa.end_state.add_transition(nil, nfa.end_state)
end
nfa
end
end
end
class CharacterRangeUnit < Unit
attr_accessor :min_code_point
attr_accessor :max_code_point
def initialize(c1, c2 = nil)
@min_code_point = c1.ord
@max_code_point = c2 ? c2.ord : @min_code_point
end
def range
@min_code_point..@max_code_point
end
def to_nfa
nfa = NFA.new
nfa.start_state.add_transition(range, nfa.end_state)
nfa
end
end
class CharacterClassUnit < Unit
attr_accessor :units
attr_accessor :negate
def initialize
@units = []
@negate = false
end
def initialize
@units = []
end
def method_missing(*args)
@units.__send__(*args)
end
def last_unit
@units[-1]
end
def replace_last!(new_unit)
@units[-1] = new_unit
end
def to_nfa
nfa = NFA.new
if @units.empty?
nfa.start_state.add_transition(nil, nfa.end_state)
else
ranges = @units.map(&:range)
if @negate
ranges = negate_ranges(ranges)
end
ranges.each do |range|
nfa.start_state.add_transition(range, nfa.end_state)
end
end
nfa
end
private
def negate_ranges(ranges)
ranges = ranges.sort_by(&:first)
new_ranges = []
last_cp = -1
ranges.each do |range|
if range.first > (last_cp + 1)
new_ranges << ((last_cp + 1)..(range.first - 1))
last_cp = range.last
end
end
if last_cp < 0xFFFFFFFF
new_ranges << ((last_cp + 1)..0xFFFFFFFF)
end
new_ranges
end
end
class MultiplicityUnit < Unit
attr_accessor :unit
attr_accessor :min_count
attr_accessor :max_count
def initialize(unit, min_count, max_count)
@unit = unit
@min_count = min_count
@max_count = max_count
end
def to_nfa
nfa = NFA.new
unit_nfa = @unit.to_nfa
nfa.start_state.add_transition(nil, unit_nfa.start_state)
if @min_count == 0
nfa.start_state.add_transition(nil, nfa.end_state)
else
(@min_count - 1).times do
prev_nfa = unit_nfa
unit_nfa = @unit.to_nfa
prev_nfa.end_state.add_transition(nil, unit_nfa.start_state)
end
end
unit_nfa.end_state.add_transition(nil, nfa.end_state)
if @max_count.nil?
unit_nfa.end_state.add_transition(nil, nfa.start_state)
else
(@max_count - @min_count).times do
prev_nfa = unit_nfa
unit_nfa = @unit.to_nfa
prev_nfa.end_state.add_transition(nil, unit_nfa.start_state)
unit_nfa.end_state.add_transition(nil, nfa.end_state)
end
end
nfa
end
end
end
end