OSDN Git Service

ruby-1.9.1-rc1
[splhack/AndroidRuby.git] / lib / ruby-1.9.1-rc1 / tool / transcode-tblgen.rb
1 require 'optparse'
2 require 'erb'
3 require 'fileutils'
4
5 NUM_ELEM_BYTELOOKUP = 2
6
7 C_ESC = {
8   "\\" => "\\\\",
9   '"' => '\"',
10   "\n" => '\n',
11 }
12
13 0x00.upto(0x1f) {|ch| C_ESC[[ch].pack("C")] ||= "\\%03o" % ch }
14 0x7f.upto(0xff) {|ch| C_ESC[[ch].pack("C")] = "\\%03o" % ch }
15 C_ESC_PAT = Regexp.union(*C_ESC.keys)
16
17 def c_esc(str)
18   '"' + str.gsub(C_ESC_PAT) { C_ESC[$&] } + '"'
19 end
20
21 class StrSet
22   def self.parse(pattern)
23     if /\A\s*(([0-9a-f][0-9a-f]|\{([0-9a-f][0-9a-f]|[0-9a-f][0-9a-f]-[0-9a-f][0-9a-f])(,([0-9a-f][0-9a-f]|[0-9a-f][0-9a-f]-[0-9a-f][0-9a-f]))*\})+(\s+|\z))*\z/i !~ pattern
24       raise ArgumentError, "invalid pattern: #{pattern.inspect}"
25     end
26     result = []
27     pattern.scan(/\S+/) {|seq|
28       seq_result = []
29       while !seq.empty?
30         if /\A([0-9a-f][0-9a-f])/i =~ seq
31           byte = $1.to_i(16)
32           seq_result << [byte..byte]
33           seq = $'
34         elsif /\A\{([^\}]+)\}/ =~ seq
35           set = $1
36           seq = $'
37           set_result = []
38           set.scan(/[^,]+/) {|range|
39             if /\A([0-9a-f][0-9a-f])-([0-9a-f][0-9a-f])\z/i =~ range
40               b = $1.to_i(16)
41               e = $2.to_i(16)
42               set_result << (b..e)
43             elsif /\A([0-9a-f][0-9a-f])\z/i =~ range
44               byte = $1.to_i(16)
45               set_result << (byte..byte)
46             else
47               raise "invalid range: #{range.inspect}"
48             end
49           }
50           seq_result << set_result
51         else
52           raise "invalid sequence: #{seq.inspect}"
53         end
54       end
55       result << seq_result
56     }
57     self.new(result)
58   end
59
60   def initialize(pat)
61     @pat = pat
62   end
63
64   def hash
65     return @hash if defined? @hash
66     @hash = @pat.hash
67   end
68
69   def eql?(other)
70     self.class == other.class &&
71     @pat == other.instance_eval { @pat }
72   end
73
74   alias == eql?
75
76   def to_s
77     if @pat.empty?
78       "(empset)"
79     else
80       @pat.map {|seq|
81         if seq.empty?
82           "(empstr)"
83         else
84           seq.map {|byteset|
85             if byteset.length == 1 && byteset[0].begin == byteset[0].end
86               "%02x" % byteset[0].begin
87             else
88               "{" + 
89               byteset.map {|range|
90                 if range.begin == range.end
91                   "%02x" % range.begin
92                 else
93                   "%02x-%02x" % [range.begin, range.end]
94                 end
95               }.join(',') +
96               "}"
97             end
98           }.join('')
99         end
100       }.join(' ')
101     end
102   end
103
104   def inspect
105     "\#<#{self.class}: #{self.to_s}>"
106   end
107
108   def min_length
109     if @pat.empty?
110       nil
111     else
112       @pat.map {|seq| seq.length }.min
113     end
114   end
115
116   def max_length
117     if @pat.empty?
118       nil
119     else
120       @pat.map {|seq| seq.length }.max
121     end
122   end
123
124   def emptyable?
125     @pat.any? {|seq|
126       seq.empty?
127     }
128   end
129
130   def first_bytes
131     result = {}
132     @pat.each {|seq|
133       next if seq.empty?
134       seq.first.each {|range|
135         range.each {|byte|
136           result[byte] = true
137         }
138       }
139     }
140     result.keys.sort
141   end
142
143   def each_firstbyte
144     h = {}
145     @pat.each {|seq|
146       next if seq.empty?
147       seq.first.each {|range|
148         range.each {|byte|
149           (h[byte] ||= []) << seq[1..-1]
150         }
151       }
152     }
153     h.keys.sort.each {|byte|
154       yield byte, StrSet.new(h[byte])
155     }
156   end
157 end
158
159 class ArrayCode
160   def initialize(type, name)
161     @type = type
162     @name = name
163     @len = 0;
164     @content = ''
165   end
166
167   def length
168     @len
169   end
170
171   def insert_at_last(num, str)
172     newnum = self.length + num
173     @content << str
174     @len += num
175   end
176
177   def to_s
178     <<"End"
179 static const #{@type}
180 #{@name}[#{@len}] = {
181 #{@content}};
182 End
183   end
184 end
185
186 class ActionMap
187   def self.parse(hash)
188     h = {}
189     hash.each {|pat, action|
190       h[StrSet.parse(pat)] = action
191     }
192     self.new(h)
193   end
194
195   def initialize(h)
196     @map = h
197   end
198
199   def hash
200     return @hash if defined? @hash
201     hash = 0
202     @map.each {|k,v|
203       hash ^= k.hash ^ v.hash
204     }
205     @hash = hash
206   end
207
208   def eql?(other)
209     self.class == other.class &&
210     @map == other.instance_eval { @map }
211   end
212
213   alias == eql?
214
215   def inspect
216     "\#<#{self.class}:" + 
217     @map.map {|k, v| " [" + k.to_s + "]=>" + v.inspect }.join('') +
218     ">"
219   end
220
221   def max_input_length
222     @map.keys.map {|k| k.max_length }.max
223   end
224
225   def empty_action
226     @map.each {|ss, action|
227       return action if ss.emptyable?
228     }
229     nil
230   end
231
232   def each_firstbyte(valid_encoding=nil)
233     h = {}
234     @map.each {|ss, action|
235       if ss.emptyable?
236         raise "emptyable pattern"
237       else
238         ss.each_firstbyte {|byte, rest|
239           h[byte] ||= {}
240           if h[byte][rest]
241             raise "ambiguous %s or %s (%02X/%s)" % [h[byte][rest], action, byte, rest]
242           end
243           h[byte][rest] = action
244         }
245       end
246     }
247     if valid_encoding
248       valid_encoding.each_firstbyte {|byte, rest|
249         if h[byte]
250           am = ActionMap.new(h[byte])
251           yield byte, am, rest
252         else
253           am = ActionMap.new(rest => :undef)
254           yield byte, am, nil
255         end
256       }
257     else
258       h.keys.sort.each {|byte|
259         am = ActionMap.new(h[byte])
260         yield byte, am, nil
261       }
262     end
263   end
264
265   OffsetsMemo = {}
266   InfosMemo = {}
267
268   def format_offsets(min, max, offsets)
269     offsets = offsets[min..max]
270     code = "%d, %d,\n" % [min, max]
271     0.step(offsets.length-1,16) {|i|
272       code << "    "
273       code << offsets[i,8].map {|off| "%3d," % off.to_s }.join('')
274       if i+8 < offsets.length
275         code << "  "
276         code << offsets[i+8,8].map {|off| "%3d," % off.to_s }.join('')
277       end
278       code << "\n"
279     }
280     code
281   end
282
283   UsedName = {}
284
285   StrMemo = {}
286
287   def str_name(bytes)
288     size = @bytes_code.length
289     rawbytes = [bytes].pack("H*")
290
291     n = nil
292     if !n && !(suf = rawbytes.gsub(/[^A-Za-z0-9_]/, '')).empty? && !UsedName[nn = "str1_" + suf] then n = nn end
293     if !n && !UsedName[nn = "str1_" + bytes] then n = nn end
294     n ||= "str1s_#{size}"
295
296     StrMemo[bytes] = n
297     UsedName[n] = true
298     n
299   end
300
301   def gen_str(bytes)
302     if n = StrMemo[bytes]
303       n
304     else
305       len = bytes.length/2
306       size = @bytes_code.length
307       n = str_name(bytes)
308       @bytes_code.insert_at_last(1 + len,
309         "\#define #{n} makeSTR1(#{size})\n" +
310         "    makeSTR1LEN(#{len})," + bytes.gsub(/../, ' 0x\&,') + "\n\n")
311       n
312     end
313   end
314
315   def generate_info(info)
316     case info
317     when :nomap
318       "NOMAP"
319     when :undef
320       "UNDEF"
321     when :invalid
322       "INVALID"
323     when :func_ii
324       "FUNii"
325     when :func_si
326       "FUNsi"
327     when :func_io
328       "FUNio"
329     when :func_so
330       "FUNso"
331     when /\A([0-9a-f][0-9a-f])\z/i
332       "o1(0x#$1)"
333     when /\A([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])\z/i
334       "o2(0x#$1,0x#$2)"
335     when /\A([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])\z/i
336       "o3(0x#$1,0x#$2,0x#$3)"
337     when /\A(f[0-7])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])\z/i
338       "o4(0x#$1,0x#$2,0x#$3,0x#$4)"
339     when /\A([0-9a-f][0-9a-f]){4,259}\z/i
340       gen_str(info.upcase)
341     when /\A\/\*BYTE_LOOKUP\*\// # pointer to BYTE_LOOKUP structure
342       $'.to_s
343     else
344       raise "unexpected action: #{info.inspect}"
345     end
346   end
347
348   def format_infos(infos)
349     infos = infos.map {|info| generate_info(info) }
350     maxlen = infos.map {|info| info.length }.max
351     columns = maxlen <= 16 ? 4 : 2
352     code = ""
353     0.step(infos.length-1, columns) {|i|
354       code << "    "
355       is = infos[i,columns]
356       is.each {|info|
357         code << sprintf(" %#{maxlen}s,", info)
358       }
359       code << "\n"
360     }
361     code
362   end
363
364   def generate_lookup_node(bytes_code, words_code, name, table)
365     offsets = []
366     infos = []
367     infomap = {}
368     min = max = nil
369     table.each_with_index {|action, byte|
370       action ||= :invalid
371       if action != :invalid
372         min = byte if !min
373         max = byte
374       end
375       unless o = infomap[action]
376         infomap[action] = o = infos.length
377         infos[o] = action
378       end
379       offsets[byte] = o
380     }
381     if !min
382       min = max = 0
383     end
384
385     offsets_key = [min, max, offsets[min..max]]
386     if n = OffsetsMemo[offsets_key]
387       offsets_name = n
388     else
389       offsets_name = "#{name}_offsets"
390       OffsetsMemo[offsets_key] = offsets_name
391       size = bytes_code.length
392       bytes_code.insert_at_last(2+max-min+1,
393         "\#define #{offsets_name} #{size}\n" +
394         format_offsets(min,max,offsets) + "\n")
395     end
396
397     if n = InfosMemo[infos]
398       infos_name = n
399     else
400       infos_name = "#{name}_infos"
401       InfosMemo[infos] = infos_name
402
403       size = words_code.length
404       words_code.insert_at_last(infos.length,
405         "\#define #{infos_name} WORDINDEX2INFO(#{size})\n" +
406         format_infos(infos) + "\n")
407     end
408
409     size = words_code.length
410     words_code.insert_at_last(NUM_ELEM_BYTELOOKUP,
411       "\#define #{name} WORDINDEX2INFO(#{size})\n" +
412       <<"End" + "\n")
413     #{offsets_name},
414     #{infos_name},
415 End
416   end
417
418   PreMemo = {}
419   PostMemo = {}
420   NextName = "a"
421
422   def generate_node(bytes_code, words_code, name_hint=nil, valid_encoding=nil)
423     if n = PreMemo[[self,valid_encoding]]
424       return n
425     end
426
427     table = Array.new(0x100, :invalid)
428     each_firstbyte(valid_encoding) {|byte, rest, rest_valid_encoding|
429       if a = rest.empty_action
430         table[byte] = a
431       else
432         name_hint2 = nil
433         name_hint2 = "#{name_hint}_#{'%02X' % byte}" if name_hint
434         table[byte] = "/*BYTE_LOOKUP*/" + rest.generate_node(bytes_code, words_code, name_hint2, rest_valid_encoding)
435       end
436     }
437
438     if n = PostMemo[table]
439       return n
440     end
441
442     if !name_hint
443       name_hint = "fun_" + NextName.dup
444       NextName.succ!
445     end
446
447     PreMemo[[self,valid_encoding]] = PostMemo[table] = name_hint
448
449     generate_lookup_node(bytes_code, words_code, name_hint, table)
450     name_hint
451   end
452
453   def gennode(bytes_code, words_code, name_hint=nil, valid_encoding=nil)
454     @bytes_code = bytes_code
455     @words_code = words_code
456     name = generate_node(bytes_code, words_code, name_hint, valid_encoding)
457     @bytes_code = nil
458     @words_code = nil
459     return name
460   end
461 end
462
463 def citrus_mskanji_cstomb(csid, index)
464   case csid
465   when 0
466     index
467   when 1
468     index + 0x80
469   when 2, 3
470     row = index >> 8
471     raise "invalid byte sequence" if row < 0x21
472     if csid == 3
473       if row <= 0x2F
474         offset = (row == 0x22 || row >= 0x26) ? 0xED : 0xF0
475       elsif row >= 0x4D && row <= 0x7E
476         offset = 0xCE
477       else
478         raise "invalid byte sequence"
479       end
480     else
481       raise "invalid byte sequence" if row > 0x97
482       offset = (row < 0x5F) ? 0x81 : 0xC1
483     end
484     col = index & 0xFF
485     raise "invalid byte sequence" if (col < 0x21 || col > 0x7E)
486
487     row -= 0x21
488     col -= 0x21
489     if (row & 1) == 0
490       col += 0x40
491       col += 1 if (col >= 0x7F)
492     else
493       col += 0x9F;
494     end
495     row = row / 2 + offset
496     (row << 8) | col
497   end.to_s(16)
498 end
499
500 def citrus_euc_cstomb(csid, index)
501   case csid
502   when 0x0000
503     index
504   when 0x8080
505     index | 0x8080
506   when 0x0080
507     index | 0x8E80
508   when 0x8000
509     index | 0x8F8080
510   end.to_s(16)
511 end
512
513 def citrus_cstomb(ces, csid, index)
514   case ces
515   when 'mskanji'
516     citrus_mskanji_cstomb(csid, index)
517   when 'euc'
518     citrus_euc_cstomb(csid, index)
519   end
520 end
521
522 SUBDIR = %w/APPLE AST BIG5 CNS CP EBCDIC GB GEORGIAN ISO646 ISO-8859 JIS KAZAKH KOI KS MISC TCVN/
523
524
525 def citrus_decode_mapsrc(ces, csid, mapsrcs)
526   table = []
527   mapsrcs.split(',').each do |mapsrc|
528     path = [$srcdir]
529     mode = nil
530     if mapsrc.rindex('UCS', 0)
531       mode = :from_ucs
532       from = mapsrc[4..-1]
533       path << SUBDIR.find{|x| from.rindex(x, 0) }
534     else
535       mode = :to_ucs
536       path << SUBDIR.find{|x| mapsrc.rindex(x, 0) }
537     end
538     path << mapsrc.gsub(':', '@')
539     path = File.join(*path)
540     path << ".src"
541     path[path.rindex('/')] = '%'
542     STDERR.puts 'load mapsrc %s' % path if VERBOSE_MODE
543     open(path) do |f|
544       f.each_line do |l|
545         break if /^BEGIN_MAP/ =~ l
546       end
547       f.each_line do |l|
548         next if /^\s*(?:#|$)/ =~ l
549           break if /^END_MAP/ =~ l
550         case mode
551         when :from_ucs
552           case l
553           when /0x(\w+)\s*-\s*0x(\w+)\s*=\s*INVALID/
554             # Citrus OOB_MODE
555           when /(0x\w+)\s*=\s*(0x\w+)/
556             table.push << [$1.hex, citrus_cstomb(ces, csid, $2.hex)]
557           else
558             raise "unknown notation '%s'"% l
559           end
560         when :to_ucs
561           case l
562           when /(0x\w+)\s*=\s*(0x\w+)/
563             table.push << [citrus_cstomb(ces, csid, $1.hex), $2.hex]
564           else
565             raise "unknown notation '%s'"% l
566           end
567         end
568       end
569     end
570   end
571   return table
572 end
573
574 def encode_utf8(map)
575   r = []
576   map.each {|k, v|
577     # integer means UTF-8 encoded sequence.
578     k = [k].pack("U").unpack("H*")[0].upcase if Integer === k
579     v = [v].pack("U").unpack("H*")[0].upcase if Integer === v
580     r << [k,v]
581   }
582   r
583 end
584
585 def transcode_compile_tree(name, from, map)
586   map = encode_utf8(map)
587   h = {}
588   map.each {|k, v|
589     h[k] = v unless h[k] # use first mapping
590   }
591   am = ActionMap.parse(h)
592
593   max_input = am.max_input_length
594
595   if ValidEncoding[from]
596     valid_encoding = StrSet.parse(ValidEncoding[from])
597   else
598     valid_encoding = nil
599   end
600
601   defined_name = am.gennode(TRANSCODE_GENERATED_BYTES_CODE, TRANSCODE_GENERATED_WORDS_CODE, name, valid_encoding)
602   return defined_name, max_input
603 end
604
605 TRANSCODERS = []
606 TRANSCODE_GENERATED_TRANSCODER_CODE = ''
607
608 def transcode_tblgen(from, to, map)
609   if VERBOSE_MODE
610     if from.empty? || to.empty?
611       STDERR.puts "converter for #{from.empty? ? to : from}"
612     else
613       STDERR.puts "converter from #{from} to #{to}"
614     end
615   end
616   id_from = from.tr('^0-9A-Za-z', '_')
617   id_to = to.tr('^0-9A-Za-z', '_')
618   if from == "UTF-8"
619     tree_name = "to_#{id_to}"
620   elsif to == "UTF-8"
621     tree_name = "from_#{id_from}"
622   else
623     tree_name = "from_#{id_from}_to_#{id_to}"
624   end
625   map = encode_utf8(map)
626   real_tree_name, max_input = transcode_compile_tree(tree_name, from, map)
627   transcoder_name = "rb_#{tree_name}"
628   TRANSCODERS << transcoder_name
629   input_unit_length = UnitLength[from]
630   max_output = map.map {|k,v| String === v ? v.length/2 : 1 }.max
631   transcoder_code = <<"End"
632 static const rb_transcoder
633 #{transcoder_name} = {
634     #{c_esc from}, #{c_esc to}, #{real_tree_name},
635     TRANSCODE_TABLE_INFO,
636     #{input_unit_length}, /* input_unit_length */
637     #{max_input}, /* max_input */
638     #{max_output}, /* max_output */
639     asciicompat_converter, /* asciicompat_type */
640     0, NULL, NULL, /* state_size, state_init, state_fini */
641     NULL, NULL, NULL, NULL,
642     NULL, NULL, NULL
643 };
644 End
645   TRANSCODE_GENERATED_TRANSCODER_CODE << transcoder_code
646   ''
647 end
648
649 def transcode_generate_node(am, name_hint=nil)
650   STDERR.puts "converter for #{name_hint}" if VERBOSE_MODE
651   name = am.gennode(TRANSCODE_GENERATED_BYTES_CODE, TRANSCODE_GENERATED_WORDS_CODE, name_hint)
652   ''
653 end
654
655 def transcode_generated_code
656   TRANSCODE_GENERATED_BYTES_CODE.to_s +
657     TRANSCODE_GENERATED_WORDS_CODE.to_s +
658     "\#define TRANSCODE_TABLE_INFO " +
659     "#{OUTPUT_PREFIX}byte_array, #{TRANSCODE_GENERATED_BYTES_CODE.length}, " +
660     "#{OUTPUT_PREFIX}word_array, #{TRANSCODE_GENERATED_WORDS_CODE.length}, " +
661     "sizeof(unsigned int)\n" +
662     TRANSCODE_GENERATED_TRANSCODER_CODE
663 end
664
665 def transcode_register_code
666   code = ''
667   TRANSCODERS.each {|transcoder_name|
668     code << "    rb_register_transcoder(&#{transcoder_name});\n"
669   }
670   code
671 end
672
673 UnitLength = {
674   'UTF-16BE'    => 2,
675   'UTF-16LE'    => 2,
676   'UTF-32BE'    => 4,
677   'UTF-32LE'    => 4,
678 }
679 UnitLength.default = 1
680
681 ValidEncoding = {
682   '1byte'       => '{00-ff}',
683   '2byte'       => '{00-ff}{00-ff}',
684   '4byte'       => '{00-ff}{00-ff}{00-ff}{00-ff}',
685   'US-ASCII'    => '{00-7f}',
686   'UTF-8'       => '{00-7f}
687                     {c2-df}{80-bf}
688                          e0{a0-bf}{80-bf}
689                     {e1-ec}{80-bf}{80-bf}
690                          ed{80-9f}{80-bf}
691                     {ee-ef}{80-bf}{80-bf}
692                          f0{90-bf}{80-bf}{80-bf}
693                     {f1-f3}{80-bf}{80-bf}{80-bf}
694                          f4{80-8f}{80-bf}{80-bf}',
695   'UTF-16BE'    => '{00-d7,e0-ff}{00-ff}
696                     {d8-db}{00-ff}{dc-df}{00-ff}',
697   'UTF-16LE'    => '{00-ff}{00-d7,e0-ff}
698                     {00-ff}{d8-db}{00-ff}{dc-df}',
699   'UTF-32BE'    => '0000{00-d7,e0-ff}{00-ff}
700                     00{01-10}{00-ff}{00-ff}',
701   'UTF-32LE'    => '{00-ff}{00-d7,e0-ff}0000
702                     {00-ff}{00-ff}{01-10}00',
703   'EUC-JP'      => '{00-7f}
704                     {a1-fe}{a1-fe}
705                     8e{a1-fe}
706                     8f{a1-fe}{a1-fe}',
707   'CP51932'     => '{00-7f}
708                     {a1-fe}{a1-fe}
709                     8e{a1-fe}',
710   'Shift_JIS'   => '{00-7f}
711                     {81-9f,e0-fc}{40-7e,80-fc}
712                     {a1-df}',
713   'EUC-KR'      => '{00-7f}
714                     {a1-fe}{a1-fe}',
715   'CP949'       => '{00-7f}
716                     {81-fe}{41-5a,61-7a,81-fe}',
717   'Big5'        => '{00-7f}
718                     {81-fe}{40-7e,a1-fe}',
719   'EUC-TW'      => '{00-7f}
720                     {a1-fe}{a1-fe}
721                     8e{a1-b0}{a1-fe}{a1-fe}',
722   'GBK'         => '{00-80}
723                     {81-fe}{40-7e,80-fe}',
724   'GB18030'     => '{00-7f}
725                     {81-fe}{40-7e,80-fe}
726                     {81-fe}{30-39}{81-fe}{30-39}',
727 }
728
729 def set_valid_byte_pattern (encoding, pattern_or_label)
730   pattern =
731     if ValidEncoding[pattern_or_label]
732       ValidEncoding[pattern_or_label]
733     else
734       pattern_or_label
735     end
736   if ValidEncoding[encoding] and ValidEncoding[encoding]!=pattern
737     raise ArgumentError, "trying to change valid byte pattern for encoding #{encoding} from #{ValidEncoding[encoding]} to #{pattern}"
738   end
739   ValidEncoding[encoding] = pattern
740 end
741
742 # the following may be used in different places, so keep them here for the moment
743 set_valid_byte_pattern 'ASCII-8BIT', '1byte'
744 set_valid_byte_pattern 'Windows-31J', 'Shift_JIS'
745 set_valid_byte_pattern 'eucJP-ms', 'EUC-JP'
746
747 def make_signature(filename, src)
748   "src=#{filename.dump}, len=#{src.length}, checksum=#{src.sum}"
749 end
750
751 output_filename = nil
752 verbose_mode = false
753 force_mode = false
754
755 op = OptionParser.new
756 op.def_option("--help", "show help message") { puts op; exit 0 }
757 op.def_option("--verbose", "verbose mode") { verbose_mode = true }
758 op.def_option("--force", "force table generation") { force_mode = true }
759 op.def_option("--output=FILE", "specify output file") {|arg| output_filename = arg }
760 op.parse!
761
762 VERBOSE_MODE = verbose_mode
763
764 OUTPUT_FILENAME = output_filename
765 OUTPUT_PREFIX = output_filename ? File.basename(output_filename)[/\A[A-Za-z0-9_]*/] : ""
766 OUTPUT_PREFIX.sub!(/\A_+/, '')
767 OUTPUT_PREFIX.sub!(/_*\z/, '_')
768
769 TRANSCODE_GENERATED_BYTES_CODE = ArrayCode.new("unsigned char", "#{OUTPUT_PREFIX}byte_array")
770 TRANSCODE_GENERATED_WORDS_CODE = ArrayCode.new("unsigned int", "#{OUTPUT_PREFIX}word_array")
771
772 arg = ARGV.shift
773 $srcdir = File.dirname(arg)
774 $:.unshift $srcdir unless $:.include? $srcdir
775 src = File.read(arg)
776 src.force_encoding("ascii-8bit") if src.respond_to? :force_encoding
777 this_script = File.read(__FILE__)
778 this_script.force_encoding("ascii-8bit") if this_script.respond_to? :force_encoding
779
780 base_signature = "/* autogenerated. */\n"
781 base_signature << "/* #{make_signature(File.basename(__FILE__), this_script)} */\n"
782 base_signature << "/* #{make_signature(File.basename(arg), src)} */\n"
783
784 if !force_mode && output_filename && File.readable?(output_filename)
785   old_signature = File.open(output_filename) {|f| f.gets("").chomp }
786   chk_signature = base_signature.dup
787   old_signature.each_line {|line|
788     if %r{/\* src="([0-9a-z_.-]+)",} =~ line
789       name = $1
790       next if name == File.basename(arg) || name == File.basename(__FILE__)
791       path = File.join($srcdir, name)
792       if File.readable? path
793         chk_signature << "/* #{make_signature(name, File.read(path))} */\n"
794       end
795     end
796   }
797   if old_signature == chk_signature
798     now = Time.now
799     File.utime(now, now, output_filename)
800     STDERR.puts "already up-to-date: #{output_filename}" if VERBOSE_MODE
801     exit
802   end
803 end
804
805 if VERBOSE_MODE
806   if output_filename
807     STDERR.puts "generating #{output_filename} ..."
808   end
809 end
810
811 libs1 = $".dup
812 erb = ERB.new(src, nil, '%')
813 erb.filename = arg
814 erb_result = erb.result(binding)
815 libs2 = $".dup
816
817 libs = libs2 - libs1
818 lib_sigs = ''
819 libs.each {|lib|
820   lib = File.basename(lib)
821   path = File.join($srcdir, lib)
822   if File.readable? path
823     lib_sigs << "/* #{make_signature(lib, File.read(path))} */\n"
824   end
825 }
826
827 result = ''
828 result << base_signature
829 result << lib_sigs
830 result << "\n"
831 result << erb_result
832 result << "\n"
833
834 if output_filename
835   new_filename = output_filename + ".new"
836   FileUtils.mkdir_p(File.dirname(output_filename))
837   File.open(new_filename, "wb") {|f| f << result }
838   File.rename(new_filename, output_filename)
839   STDERR.puts "done." if VERBOSE_MODE
840 else
841   print result
842 end