1  # $Id: component.rb,v 1.3 2005/08/22 09:12:03 aamine Exp $
   2  
   3  require 'rational'
   4  require 'forwardable'
   5  
   6  class IntegerOverflow < ::StandardError; end
   7  class ParseError < ::StandardError; end
   8  
   9  class Component
  10    @LLDN = nil
  11    def Component.LLDN
  12      @LLDN ||= defcomponentlist(
  13        %w( ¡» °ì Æó »° »Í ¸Þ Ï» ¼· Ȭ ¶å ),
  14        ['̵ÎÌÂç¿ô',    8],
  15        ['ÉԲĻ׵Ä',    8],
  16        ['Æáͳ¿',      8],
  17        ['°¤ÁεÀ',      8],
  18        ['¹±²Ïº»',      8],
  19        ['¶Ë',          4],
  20        ['ºÜ',          4],
  21        ['˵',          4],
  22        ['´Â',          4],
  23        ['¹Â',          4],
  24        ['¾÷',          4],
  25        ['²Óͽ',        4],
  26        ['Ô¶',          4],
  27        ['µþ',          4],
  28        ['Ãû',          4],
  29        ['²¯',          4],
  30        ['Ëü',          1],
  31        ['Àé',          1],
  32        ['É´',          1],
  33        ['½½',          1],
  34        [nil,           0]
  35      )
  36    end
  37  
  38    def Component.defcomponentlist(nums, *deflist)
  39      term = Terminator.new(nums)
  40      list = deflist.inject(nil) {|_next, a| new(_next, *a) }
  41      list.each do |c|
  42        c.instance_eval {
  43          pow = next_pow()
  44          @branch = (pow > 1 ? list.take(pow) : term)
  45        }
  46      end
  47      Head.new(list)
  48    end
  49  
  50    include Enumerable
  51  
  52    def initialize(_next, unit, pow)
  53      @next = _next
  54      @unit = unit
  55      @pow = pow
  56    end
  57  
  58    attr_reader :unit
  59    attr_reader :pow
  60    attr_reader :branch
  61  
  62    def inspect
  63      "#<Component #{@unit.to_s}>"
  64    end
  65  
  66    def next_pow
  67      @next ? @next.pow : 8
  68    end
  69  
  70    def each(&block)
  71      yield self
  72      @next.each(&block) if @next
  73    end
  74  
  75    def length
  76      inject(0) {|len, i| len + 1 }
  77    end
  78  
  79    def take(n)
  80      if n - @pow > 0
  81      then dup(@next.take(n - @pow))
  82      else nil
  83      end
  84    end
  85  
  86    def dup(_next = @next, branch = @branch)
  87      obj = self.class.new(_next, @unit, @pow)
  88      obj.instance_eval { @branch = branch }
  89      obj
  90    end
  91  
  92    def chars_re
  93      @chars_re ||= /#{Regexp.union(*chars())}+/
  94    end
  95  
  96    def chars
  97      inject([]) {|set, c| set + [c.chars1] }.flatten.uniq.compact
  98    end
  99  
 100    def chars1
 101      @branch.inject([@unit]) {|set, c| set + c.chars1 }
 102    end
 103  
 104    def parse(str)
 105      if m = regexp().match(str)
 106      then upnum(m.pre_match) + @branch.parse(m[1])
 107      else upnum(str)
 108      end
 109    end
 110  
 111    def upnum(s)
 112      return 0 if s.empty?
 113      raise IntegerOverflow, "integer too big" unless @next
 114      @next.parse(s) * (10 ** @next.pow)
 115    end
 116    private :upnum
 117  
 118    def regexp
 119      @re ||= /(#{@branch.pattern})#{@unit ? '?' : ''}#{@unit.to_s}\z/
 120    end
 121    private :regexp
 122  
 123    def pattern
 124      (@next ? @next.pattern : '') + "(?:(#{@branch.pattern})?#{@unit.to_s})?"
 125    end
 126  
 127    def string_expr(n)
 128      up, base = *n.divmod(10 ** next_pow())
 129      upstr(up) + basestr(base)
 130    end
 131  
 132    def upstr(n)
 133      unless @next
 134        return '' if n == 0
 135        raise IntegerOverflow, "integer too big"
 136      end
 137      @next.string_expr(n)
 138    end
 139    private :upstr
 140  
 141    def basestr(n)
 142      if n == 0
 143      then ''
 144      else "#{@branch.string_expr(n)}#{@unit}"
 145      end
 146    end
 147    private :basestr
 148  end
 149  
 150  class Terminator
 151    include Enumerable
 152  
 153    def initialize(nums)
 154      @nums = nums
 155      @re = nil
 156    end
 157  
 158    def inspect
 159      "#<TERM>"
 160    end
 161  
 162    def each
 163      yield self
 164    end
 165  
 166    def chars1
 167      @nums
 168    end
 169  
 170    def pattern
 171      "[#{@nums.join()}]"
 172    end
 173  
 174    def parse(str)
 175      return Rational(1) unless str
 176      return Rational(1) if str.empty?
 177      n = @nums.index(str) or
 178          raise ParseError, "unexpected string: #{str.inspect}"
 179      Rational(n)
 180    end
 181  
 182    def string_expr(n)
 183      @nums[n]
 184    end
 185  end
 186  
 187  class Head
 188    def initialize(list)
 189      @list = list
 190    end
 191  
 192    def inspect
 193      "#<HEAD>"
 194    end
 195  
 196    extend Forwardable
 197    def_delegator "@list", :chars_re
 198    def_delegator "@list", :parse
 199    
 200    def string_expr(n)
 201      str = @list.string_expr(n.to_i.abs).gsub(/°ì(?=[ÀéÉ´½½])/, '')
 202      str = '¡»' if str.empty?
 203      (n.to_i < 0 ? 'Éé' : '') + str + (n.denominator==1 ? '' : ';')
 204    end
 205  end