add config file
[chise/ruby.git] / lib / chise / char.rb
1 #!/usr/bin/env ruby
2 # Ruby/CHISE module by eto 2002-1114
3 # $Id: char.rb,v 1.3 2003-11-30 13:16:38 eto Exp $
4 # Copyright (C) 2002-2003 Kouichirou Eto, All rights reserved.
5 # This is free software with ABSOLUTELY NO WARRANTY.
6 # You can redistribute it and/or modify it under the terms of the GNU GPL2.
7
8 require 'uconv'
9 require 'singleton'
10 $LOAD_PATH << '../../lib' if $0 == __FILE__
11 require 'chise/db'
12 require 'chise/config'
13
14 #$KCODE = 'u' #今のところこれ以外では動かない。String.splitが影響大。inspectも影響。
15 $debug = false #これはテスト用
16 $debug = true #これはテスト用
17 $stdout.binmode if $debug
18 $stdout.sync = true if $debug
19
20 class String #======================================================================
21   def to_a()
22     k = $KCODE; $KCODE = 'u'
23     ar = self.split(//) #$KCODEによりUTF-8的な一文字づつがarに入る
24     $KCODE = k
25     ar
26   end
27   def each_char() to_a.each {|ch| yield ch } end
28   def each_character() to_a.each {|ch| yield ch.char } end
29   def char_length() to_a.length end
30   def char_at(n) to_a()[n] end
31   def first_char() to_a[0] end
32   def char() Character.get(to_a[0]) end
33   #alias to_c char #悩み中
34   #def char_id() char.char_id() end #なんとなく廃止
35   #def get_char_attribute(a) char.get_char_attribute(a) end #なんとなく廃止
36   #def ucs() char.ucs() end
37   def to_utf8()
38     return to_a.map {|ch|
39       ch.char.to_utf8
40     }.join('')
41   end
42
43   def map_char(block = Proc.new)
44     return unless block_given?
45     return self.to_a.map {|ch| (block.call(ch)).to_s }.join("")
46   end
47   def map_char!(block = Proc.new)
48     return unless block_given?
49     self.replace(self.map_char {|ch| block.call(ch)})
50   end
51   def map_character(block = Proc.new)
52     return unless block_given?
53     return self.to_a.map {|ch| (block.call(ch.char)).to_s }.join("")
54   end
55   def map_character!(block = Proc.new)
56     return unless block_given?
57     self.replace(self.map_char {|ch| block.call(ch.char)})
58   end
59
60   def method_missing(mid, *args)
61     if char_length == 1 #省略形が有効なのは、一文字の時だけ
62       char.method_missing(mid, *args)
63     else
64       raise NameError, "undefined method `#{mid.id2name}'", caller(1)
65     end
66   end
67
68   def map_utf8() map_char {|ch| ch.char.map_utf8 } end
69   alias map_ucs map_utf8
70   def map_ucs_er() map_char {|ch| ch.char.map_ucs_er } end
71   def to_er() map_char {|ch| ch.char.to_er } end
72
73   #put関係、[]関係は用意しないことにした。
74   def de_er!() #EntityReferenceを取り除く
75     return self unless self =~ Regexp.new(EntityReference::REGEXP_PART) #それらしいのが無ければ何もしない
76     er = "&"+$1+";"      
77     self.sub!(Regexp.new(Regexp.escape(er)), Character.new(er).mcs_utf8) #変換自体はCharacterにまかせる
78     return self.de_er! if self =~ Regexp.new(EntityReference::REGEXP_PART) #まだあったら再帰
79     return self
80   end
81   def de_er() return self.dup.de_er!; end
82
83   def inspect_all() map_char {|ch| ch.char.inspect_all } end
84   def inspect_x()   map_char {|ch| ch.char.inspect_x   } end
85
86 #  def to_euc()   map_char {|ch| ch.char.to_euc   } end
87   def map_euc()  map_char {|ch| ch.char.map_euc  } end
88 #  def to_sjis()  map_char {|ch| ch.char.to_sjis  } end
89   def map_sjis() map_char {|ch| ch.char.map_sjis } end
90
91   def glyph_decompose() map_char {|ch| ch.char.glyph_decompose } end
92   def decompose() map_char {|ch| ch.char.decompose } end
93   def decompose!() self.replace(self.decompose); self; end
94   def decompose_all_nu(level=nil)
95     level = 0 if level.nil?
96     if 10 < level
97       p ['too many recursive', self] 
98       exit
99     end
100     de = self.decompose
101     return de.decompose_all(level+1) if de != self #なにか変化があったから再帰
102     de #もうこれ以上変化は無さそうだぞと。
103   end
104   def decompose_all() map_char {|ch| ch.char.decompose_all } end
105   def decompose_all!() self.replace(self.decompose_all); self; end
106
107   def find() #"日雲"→"曇"とかいう感じの操作
108     ar = []
109     length = char_length()
110     each_char {|ch|
111       char = ch.char
112       ar << char.ids_contained #その文字を含んでいる漢字のリスト
113     }
114     h = Hash.new(0)
115     ar.each {|list|
116       next if list.nil?
117       list.each_char {|ch|
118         h[ch] += 1
119       }
120     }
121     str = ""
122     h.each {|k, v|
123       #      p [k, v]
124       if length == v #全部に顔を出していたら
125         str += k
126       end
127     }
128     #    p str
129     str
130   end
131   def compose()
132     db = CodesysDB.instance
133     composed = db.get('ids', self)
134     return "" if composed.nil? #なかったよと。
135     return "" if composed.char_length == 0 #なにごと?
136     return composed if composed.char_length == 1
137     composed.each_char {|ch|
138       char = ch.char
139       return ch if char.has_attribute? #とりあえず最初にみつかったものを返すというヌルい仕様
140     }
141     return "" #attributeを持つものが一つも無かったら、""にする
142   end
143   def aggregate()
144     #selfである文字列をIDSだと仮定し、それを完全にcomposeしきらないで、
145     #その部分集合だけをとりだして、compose可能であればできるだけcomposeする。
146     tree = IDS_Tree.new(self)
147     return self if tree.depth <= 1 #sub_nodesが無い場合はここでさよなら
148     tree.sub_nodes.each {|node|
149       c = node.compose
150       next if c.nil? || c == ""
151       #      print "#{self}     #{node} #{c}\n"
152       #      p [self, node, c]
153       n = self.gsub(node, c)
154       return n.aggregate
155     }
156     return self #おきかえられるものがまったくなかったら、自分をかえす。
157   end
158 end
159
160 module CHISE #======================================================================
161   def windows?()
162     (RUBY_PLATFORM =~ /cygwin/ || RUBY_PLATFORM =~ /mswin32/ || RUBY_PLATFORM =~ /mingw32/)
163   end
164   module_function :windows?
165   if windows?()
166     DB_DIR = Config.instance.db_dir #この後に/sysmtem-char-id/ucsという感じに続く
167     IDS_DB_DIR = Config.instance.ids_dir #この後にIDS-JIS-X0208-1990.txtという感じに続く
168   else
169     DB_DIR = '/usr/local/lib/xemacs-21.4.12/i686-pc-linux/char-db' #この後に/sysmtem-char-id/ucsという感じに続く
170     IDS_DB_DIR = '/home/eto/chise/ids/' #この後にIDS-JIS-X0208-1990.txtという感じに続く
171   end
172
173   class EntityReference #======================================================================
174     #状況によってどのERに変換するかが異なる可能性があるので、普通のclassとして実装したほうがいい?
175 #    CODESYS_TABLE = [
176 #      %w( chinese-big5-cdp     CDP- 4 X),
177 #      %w( ideograph-daikanwa   M-   5 d),
178 #      %w( ideograph-cbeta      CB   5 d),
179 #      %w( ideograph-gt         GT-  5 d),
180 #      %w( ideograph-gt-k       GT-K 5 d),
181 #      %w( japanese-jisx0208-1990 J90- 4 X),
182 #      %w( japanese-jisx0208    J83- 4 X),
183 #      %w( japanese-jisx0213-1  JX1- 4 X),
184 #      %w( japanese-jisx0213-2  JX2- 4 X),
185 #      %w( japanese-jisx0212    JSP- 4 X),
186 #      %w( japanese-jisx0208-1978 J78- 4 X),
187 #      %w( chinese-cns11643-1   C1-  4 X),
188 #      %w( chinese-cns11643-2   C2-  4 X),
189 #      %w( chinese-cns11643-3   C3-  4 X),
190 #      %w( chinese-cns11643-4   C4-  4 X),
191 #      %w( chinese-cns11643-5   C5-  4 X),
192 #      %w( chinese-cns11643-6   C6-  4 X),
193 #      %w( chinese-cns11643-7   C7-  4 X),
194 #      %w( korean-ksc5601       K0- 4 X),
195 #    ]
196 #    CODESYS_ORDER = %w(japanese chinese korean ideograph)
197     CODESYS_TABLE = [
198       %w( =jis-x0208-1990       J90- 4 X),
199       %w( =jis-x0208-1983       J83- 4 X),
200       %w( =jis-x0208-1978       J78- 4 X),
201       %w( =jis-x0208            J90- 4 X), #継承のアドホックな実装
202       %w( =jis-x0208            J83- 4 X), #継承のアドホックな実装
203       %w( =jis-x0208            J78- 4 X), #継承のアドホックな実装
204       %w( =jis-x0213-1-2000     JX1- 4 X),
205       %w( =jis-x0213-2-2000     JX2- 4 X),
206       %w( =jis-x0212            JSP- 4 X),
207       %w( =big5-cdp             CDP- 4 X),
208       %w( =cns11643-1           C1-  4 X),
209       %w( =cns11643-2           C2-  4 X),
210       %w( =cns11643-3           C3-  4 X),
211       %w( =cns11643-4           C4-  4 X),
212       %w( =cns11643-5           C5-  4 X),
213       %w( =cns11643-6           C6-  4 X),
214       %w( =cns11643-7           C7-  4 X),
215       %w( =ks-x1001             K0-  4 X),
216       %w( =daikanwa             M-   5 d),
217       %w( =cbeta                CB   5 d),
218       %w( =gt                   GT-  5 d),
219       %w( =gt-k                 GT-K 5 d),
220     ]
221     REGEXP_PART = "&([-+0-9A-Za-z#]+);"
222     REGEXP_ALL = "^#{REGEXP_PART}$"
223
224     def self.match?(er) (er =~ Regexp.new(REGEXP_PART)) != nil end
225
226     def self.parse(er) #char_idをFIXNUMで返す
227       return "" unless er =~ Regexp.new(REGEXP_ALL) #なんか、間違ってる?
228       er = $1 #ついでに中身の部分を取り出す
229       return $1.hex if er =~ /^MCS-([0-9A-Fa-f]+)/ #MCS
230       #      if er =~ /^U[-+]?([0-9A-Fa-f]+)/ #Unicode直打ち
231       if er =~ /^U[-+]?([0-9A-Fa-f]+)/ || er =~ /^#x([0-9A-Fa-f]+)/ #Unicode直打ち
232         return $1.hex 
233       elsif er =~ /^#([0-9]+)/ #Unicode直打ち
234         return $1.to_i
235       elsif er =~ /^my-([0-9]+)/i #マイ文字コード
236         return (0xe000 + $1.to_i)
237       end
238
239       er.sub!(/^I-/, '') if er =~ /^I-/ #I-がついてるとどう違うのかはよくわからない
240       each_codesys {|codesys, er_prefix, keta, numtype| #p [codesys, er_prefix, keta, numtype]
241         numtyperegex = '\d' #if numtype == 'd'
242         numtyperegex = '[0-9A-Fa-f]' if numtype == 'X'
243         regexpstr = "^#{er_prefix}(#{numtyperegex}{#{keta},#{keta}})$"
244         if er =~ Regexp.new(regexpstr)
245           codestr = $1
246           code = codestr.to_i #if numtype == 'd'
247           code = codestr.hex if numtype == 'X'
248           char_id_u8 = EntityReference.get_database(codesys, code)
249           char_id_num = Character.parse_char_id(char_id_u8)
250           next if char_id_num == nil
251           return char_id_num
252         end
253       }
254       return ""
255     end
256
257     def self.each_codesys()
258 #      CODESYS_ORDER.each {|lang|
259 #       CODESYS_TABLE.each {|codesys, er_prefix, keta, numtype| #普通こういう書き方はしない。ループ一個にする。
260 #         next unless codesys =~ lang
261 #         yield(codesys, er_prefix, keta, numtype)
262 #       }
263 #      }
264       CODESYS_TABLE.each {|codesys, er_prefix, keta, numtype|
265         yield(codesys, er_prefix, keta, numtype)
266       }
267     end
268     def self.get_database(codesys, code)
269       c = CodesysDB.instance.get(codesys, code)
270       return c if c != nil
271 #      if codesys =~ /-jisx0208/
272 #       #return self.get_database("=jis-x0208", code) #再帰でどうだ?
273 #       c = CodesysDB.instance.get("=jis-x0208", code)
274 #       return c
275 #      end
276       return nil
277     end
278   end
279
280   class CharacterFactory #============================================文字オブジェクトの生成、cache
281     include Singleton
282     MAX = 10000
283     def initialize
284       @max = MAX
285       reset()
286     end
287     def get(char_id)
288       check_max()
289       n = Character.parse_char_id(char_id)
290       c = @chars[n]
291       @chars[n] = Character.new(n) if @chars[n] == nil
292       return @chars[n]
293     end
294     def reset()
295       @chars = nil
296       @chars = Hash.new
297       GC.start #ガーベージコレクション
298     end
299     def length() @chars.length; end
300     def check_max()
301       reset if @max < @chars.length #MAXを超えたらresetしてしまう。乱暴じゃがcacheなのでこれでいいのだ。
302     end
303   end
304
305   class Character #=============================================================== 文字オブジェクト
306     def initialize(char_id=nil)
307       @char_id = Character.parse_char_id(char_id)
308       @attributes = Hash.new
309       @check_all_database = false
310     end
311     attr_reader :char_id
312     def to_i() @char_id end
313     def mcs_utf8() Character.u4itou8(@char_id) end
314     def mcs_hex() sprintf("%x", @char_id) end
315
316     #----------------------------------------------------------------------
317     def self.get(char_id) CharacterFactory.instance.get(char_id) end #flyweightパターン
318
319     #----------------------------------------------------------------------
320     def normalize_attribute_name(b)
321       a = b.dup
322       a.gsub!(/_/, '-') #underlineは-に置換
323       a.sub!(/-at-/,  '@')
324       a.sub!(/^map-/,  '=>')
325       a.sub!(/^to-/,   '->')
326       a.sub!(/^from-/, '<-')
327       a
328     end
329     def get_char_attribute(b) # XEmacs UTF-2000互換API群
330       a = normalize_attribute_name(b)
331       #p [a, b]
332       atr = @attributes[a]
333       return atr if atr
334       atr = check_database(a)
335       if atr
336         @attributes[a] = atr
337         return atr
338       end
339       return get_char_attribute("="+a) unless a =~ /^=/ #頭に=がついてない場合はそれが省略されていることを仮定して、再帰する
340       nil
341     end
342     def put_char_attribute(b,v)
343       a = normalize_attribute_name(b)
344       @attributes[a] = v;
345       CharDB.instance.put(a, mcs_utf8(), v)
346     end
347     def char_attribute_alist() check_all_database(); @attributes; end
348     def char_attribute_list()  check_all_database(); @attributes.keys; end
349     alias [] get_char_attribute  #その略称
350     alias []= put_char_attribute
351     alias alist char_attribute_alist
352     alias list  char_attribute_list
353
354     def method_missing(mid, *args) #参考:ostruct.rb
355       mname = mid.id2name
356       return get_char_attribute(mname) if args.length == 0
357       put_char_attribute(mname.chop, args[0]) if mname =~ /=$/ #代入
358     end
359
360     def has_attribute?() #意味のあるattributeを持ってますか?
361       keys = list
362       keys.delete_if {|k|
363         k =~ /ids/
364       }
365       return (keys.length != 0)
366     end
367
368     #----------------------------------------------------------------------
369     def ==(ch)
370       return false if ch == nil
371       return false unless ch.is_a? Character
372       self.char_id == ch.char_id
373     end
374
375     #----------------------------------------------------------------------
376     def self.parse_char_id(char_id) #FIXNUMを返す
377       return nil if char_id == nil
378       if char_id.is_a?(Numeric) #p [char_id]
379         char_id = 0x80000000 + char_id if char_id < 0  #補数表現
380         return char_id.to_i
381       elsif char_id.is_a?(String)
382         return char_id.to_i if char_id =~ /^\d+$/ && 1 < char_id.length #文字列による数字だったら数値化してreturn
383         return EntityReference.parse(char_id) if char_id =~ Regexp.new(EntityReference::REGEXP_ALL) #実体参照?
384         char_id.sub!(/^\?/, '') if char_id =~ /^\?/ #もし先頭に?がついていたら削除
385         #このへん本当はもっとちゃんとチェックするべし
386         begin
387           u4 = Uconv.u8tou4(char_id) #UCS-4文字列に変換
388         rescue
389           p $!
390           p char_id
391           return 0
392         end
393         return Character.u4tou4i(u4) #UCS-4数値にしてreturn
394       else
395         raise ArgumentError, "unknown object for char_id", caller(1)
396       end
397     end
398     def self.u4tou4i(u4)
399       return 0 if u4 == nil || u4 == ""
400       return (u4[3] << 24 | u4[2] << 16 | u4[1] << 8 | u4[0]) #UCS-4数値にしてreturn
401     end
402     def self.u4itou4(num)
403       return "" unless num.is_a?(Integer)
404       return sprintf("%c%c%c%c", num&0xff, (num >> 8)&0xff, (num >> 16)&0xff, (num >> 24)&0xff) #UCS-4数値を文字列にしてreturn
405     end
406     def self.u4itou8(char_id) #ucsの数値を受けとり、UTF-8の文字一文字を返す
407       begin
408         u4 = Character.u4itou4(char_id)
409         u8 = Uconv.u4tou8(u4)
410         return u8
411       rescue
412         #raise ArgumentError, "invalid char_id (#{char_id})", caller(1)
413         #print "error\n"
414         return ""
415       end
416     end
417
418     #----------------------------------------------------------------------
419     def check_database(a)
420       db = CharDB.instance
421       u8 = mcs_utf8()
422       v = db.get(a, u8) #u8で表される文字のaアトリビュートを調べる。
423       return v
424     end
425     def check_all_database() #現在の@char_idから、文字データベースを参照する
426       return if @check_all_database
427       return if @char_id == nil
428       db = CharDB.instance
429       u8 = mcs_utf8()
430       atrs = db.get_all(u8) #u8で表される文字のアトリビュートを全部持ってこい
431       atrs.each {|a,v|
432         @attributes[a] = v #とかいう感じで代入するのでええかな?
433       }
434       @check_all_database = true #重い処理なので一応checkする
435     end
436
437     #----------------------------------------------------------------------
438     def ucs()      #p 'ucs'
439       #ar = %w{ucs ucs-big5 ucs-cdp ucs-cns ucs-jis ucs-ks =>ucs =>ucs* =>ucs-jis}
440       #ar = %w{ucs ucs-jis ucs-big5 ucs-cdp ucs-cns ucs-ks =>ucs =>ucs* =>ucs-jis}
441       ar = %w{ucs-jis ucs =>ucs-jis}
442       #並び順は恣意的で、ucs-jisを先に出している。本来はこれも指定できるようにするべき。
443       ar.each {|a|      #p [a]
444         u = get_char_attribute(a)
445         return u if u != nil
446       }
447       return nil
448     end
449
450     #----------------------------------------------------------------------CCS関係
451     def to_utf8() Uconv.u4tou8(Character.u4itou4(ucs())) end #UTF8文字列を返す
452     #alias to_s to_utf8
453     alias to_s mcs_utf8
454     def map_utf8()
455       u = ucs()
456       if u.nil? || 0xffff < u
457         return to_er()
458       else
459         return to_utf8()
460       end
461     end
462     alias map_ucs map_utf8
463     def map_ucs_er()
464       u = ucs()
465       if u.nil? || 0xffff < u
466         return to_er()
467       else
468         return Character.get(u).to_er()
469       end
470     end
471     def to_euc()
472       u = ucs()
473       return "" if u.nil? || 0xffff < u
474       Uconv.u16toeuc(Uconv.u4tou16(Character.u4itou4(ucs())))
475     end
476     def map_euc()
477       e = to_euc()
478       return e if e != ""
479       return to_er()
480     end
481     def to_sjis()
482       u = ucs()
483       return "" if u.nil? || 0xffff < u
484       Uconv.u16tosjis(Uconv.u4tou16(Character.u4itou4(ucs())))
485     end
486     def map_sjis()
487       e = to_sjis()
488       return e if e != ""
489       return to_er()
490     end
491
492     #----------------------------------------------------------------------
493     def to_er(codesys=nil) #実体参照を返す、希望するcodesysが引数(未実装)
494       return "" if @char_id == nil
495       return sprintf("&#x%04x;", @char_id) if @char_id <= 0xffff
496       return sprintf("&#x%05x;", @char_id) if @char_id <= 0xfffff
497       EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
498         code = self[codesys]
499         next if code == nil
500         return sprintf("&#{er_prefix}%0#{keta}#{numtype};", code)
501       }
502       return sprintf("&MCS-%08X;", @char_id) #本当はこれは無しにしたい
503     end
504     def to_er_list()
505       ar = []
506       EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
507         er = to_er(codesys)
508         ar << er if er != nil
509       }
510       ar
511     end
512
513     def inspect_x()
514       return "<>" if @char_id == nil
515       ar = [to_utf8(), to_er().sub(/^&/,'').chop]
516       "<"+ar.join(',')+">"
517     end
518     alias inspect inspect_x
519     def inspect_all_codesys() #未完成
520       #to_erを全てのcodesysにおいて実行する。その結果をコンパクトにまとめる
521     end
522     def inspect_all()
523       ar = [inspect.chop]
524       alist.to_a.sort.each {|a, v| ar << "#{a}:#{v}" }
525       return ar.join(',')+">"
526     end
527     def dump_all()
528       ar = [inspect]
529       alist.to_a.sort.each {|a, v| ar << "#{a}:#{v}" }
530       return ar.join('\n')+'\n'
531     end
532     def get_attributes()
533       str = ""
534       alist.to_a.sort.each {|a, v|
535         str += "#{a}: #{v}\n"
536       }
537       str
538     end
539
540     def inspect_ids(hex_flag=false)
541       ids = decompose
542       ar = []
543       ar << (hex_flag ? "x"+mcs_hex : to_utf8)
544       if to_s != ids #idsが部品そのものだったら部品追加はしない
545         ids.each_char {|ch|
546           char = ch.char
547           next if char.is_ids?
548           if hex_flag then
549             ar << "x"+char.mcs_hex
550           else
551             u = char.to_utf8
552             if u != ""
553               ar << u
554             else
555               ar << char.to_er
556             end
557           end
558         }
559       end
560       return "("+ar.join("\t")+")"
561     end
562
563     #----------------------------------------------------------------------IDS関係
564     def glyph_decompose() do_decompose(false) end
565     def decompose()       do_decompose(true)  end
566     def do_decompose(check_meaning = true)
567       k = self.to_s
568       #       idss = self['ids']
569       #       return idss if idss
570       #       return k if self.is_basic_kanji? #基本漢字はstop kanjiとするぞと。
571       if check_meaning
572         return self['ids-represent'] if self['ids-represent'] #ids_representを持っている場合はその値とする。
573         return self['ids-element'] if self['ids-element'] #ids_elementを持っている場合はその値とする。
574         idss = self['ids-meaning']
575         return idss if idss != nil && 0 < idss.length && k != idss
576       end
577       idss = self['ids-aggregated']
578       return idss if idss != nil && 0 < idss.length && k != idss
579       idss = self['ids']
580       return idss if idss != nil && 0 < idss.length && k != idss
581       return k
582       #       return k if idss.nil? || idss.length == 0 || k == idss
583       #       if idss.char_length == 2
584       # p ['What???', k, idss, k.inspect_all]
585       #  #return idssx[1] #二個目だけ返すとか?
586       #  return k #IDSに展開する方法が無いと。
587       #       end
588       #       return k if k == idss
589       #       if idss.include?(k) #<C5-4C4D><C6-4A37>この二文字のBUG対策
590       #  #return idss.sub(k, '')
591       #  return k #IDSに展開する方法が無いと。
592       #       end
593       #       return idss
594     end
595     def decompose_all
596       pde = ""
597       de = self.decompose #出発点
598       level = 0
599       while true
600         pde = de
601         de = pde.decompose #もう一度分解をしてみる。
602         break if pde == de #ループを抜けだす
603         exit if 10 < level #p ['too many recursive', self] 
604         level += 1
605       end
606       return de
607     end
608     def decompose_all_nu(level=nil)
609       level = 0 if level.nil?
610       if 10 < level
611         p ['too many recursive', self] 
612         exit
613       end
614       de = self.decompose
615       return de.decompose_all(level+1) if de != self #なにか変化があったから再帰
616       return de #もうこれ以上変化は無さそうだぞと。
617     end
618     def is_ids?() 0x2ff0 <= @char_id && @char_id <= 0x2fff end
619     def ids_operator_argc()
620       return 0 unless is_ids?
621       return 3 if @char_id == 0x2ff2 || @char_id == 0x2ff3
622       return 2
623     end
624   end
625
626 end
627
628 #----------------------------------------------------------------------終了