require "strscan" module HtmlFormatter class Parser def initialize @maps = [] yield self if block_given? end def map(pattern, method) @maps << [pattern, method] end def scan(subject, receiver) @scanner = StringScanner.new(subject) dispatch(receiver) until @scanner.eos? end def source_so_far @scanner.string[0...@scanner.pos] end def source_line_number [source_so_far.chomp.split(%r{\n}).count, 1].max end private def dispatch(receiver) _, method = @maps.find { |pattern, _| @scanner.scan(pattern) } raise "Unmatched sequence" unless method receiver.__send__(method, *extract_params(@scanner)) rescue => ex raise "#{ex.message} on line #{source_line_number}" end def extract_params(scanner) return [scanner[0]] unless scanner[1] params = [] i = 1 while scanner[i] params << scanner[i] i += 1 end params end end end