#! /usr/bin/env ruby # -*- coding: utf-8 -*- # # TECS merger # Merger for TECS generated templates # # Copyright (C) 2008-2017 by TOPPERS Project #-- # 上記著作権者は,以下の(1)~(4)の条件を満たす場合に限り,本ソフトウェ # ア(本ソフトウェアを改変したものを含む.以下同じ)を使用・複製・改 # 変・再配布(以下,利用と呼ぶ)することを無償で許諾する. # (1) 本ソフトウェアをソースコードの形で利用する場合には,上記の著作 # 権表示,この利用条件および下記の無保証規定が,そのままの形でソー # スコード中に含まれていること. # (2) 本ソフトウェアを,ライブラリ形式など,他のソフトウェア開発に使 # 用できる形で再配布する場合には,再配布に伴うドキュメント(利用 # 者マニュアルなど)に,上記の著作権表示,この利用条件および下記 # の無保証規定を掲載すること. # (3) 本ソフトウェアを,機器に組み込むなど,他のソフトウェア開発に使 # 用できない形で再配布する場合には,次のいずれかの条件を満たすこ # と. # (a) 再配布に伴うドキュメント(利用者マニュアルなど)に,上記の著 # 作権表示,この利用条件および下記の無保証規定を掲載すること. # (b) 再配布の形態を,別に定める方法によって,TOPPERSプロジェクトに # 報告すること. # (4) 本ソフトウェアの利用により直接的または間接的に生じるいかなる損 # 害からも,上記著作権者およびTOPPERSプロジェクトを免責すること. # また,本ソフトウェアのユーザまたはエンドユーザからのいかなる理 # 由に基づく請求からも,上記著作権者およびTOPPERSプロジェクトを # 免責すること. # # 本ソフトウェアは,無保証で提供されているものである.上記著作権者お # よびTOPPERSプロジェクトは,本ソフトウェアに関して,特定の使用目的 # に対する適合性も含めて,いかなる保証も行わない.また,本ソフトウェ # アの利用により直接的または間接的に生じたいかなる損害に関しても,そ # の責任を負わない. # # $Id$ #++ #= tecsgen : TECS のマージャ # #Authors:: 大山 博司 #Version:: #Copyright:: Copyright (C) TOPPERS Project, 2008-2017. All rights reserved. #License:: TOPPERS ライセンスに準拠 =begin tecsmerge はテンプレートファイルを元に作成されたセルタイプコード(含インライン関数を記述したセルタイプインラインヘッダ)を保守するものである。 セルタイプに以下の改変が加えられた場合に対応する。 ・受け口関数が増えた ・受け口の名前が変更された ・受け口関数の名前が変更された % tecsmerge source_dir dest_dir % tecsmerge source_files dest_dir % tecsmerge -p port_name -f old_name:new_name source_file dest_dir % tecsmerge -p old_name:new_name source_file dest_dir source_file の名前は _teml.c, _templ.h で終わっていなくてはならない dest_file は source_file の _templ を取り除いた名前でなくてはならない dest_file が存在しない場合、_templ を取り除いた名前でコピーするだけである 以下のキーワードを 含む行を探す 0) 先頭 1) /* #[]#, 2) * #[]# 3) /* #[]# 4) * #[]# 5) /* #[]# 6) { # new mode (function header is changed) * #[/]# # when --old-mode * #[/ENTRY_FUNC>]# # for Bug compatibility 7) /* #[]# 8) * #[]# 9) 終わり 上記のうち 1), 3), 5), 7) を開始キーワードと呼ぶ。 なお、特に断りのない場合、9) も開始キーワードに含む。 名前の変更 a) 受け口名の変更 -p old_port_name:new_port_name b) 受け口関数名の変更 -f old_port_name:new_port_name old_port_name, new_port_name は、次の形式でなくてはならない。 受け口名 + '_' + 関数名 i) ヘッダ1 0) と開始キーワードの間 ii) ヘッダ2 1) と開始キーワードの間 通常、これが複数存在することはないが、複数存在した場合、すべてファイル の先頭に出力される。 iii) 受け口ヘッダ 3) と開始キーワードの間 受け口ヘッダは、後続の受け口関数の受け口を表すものと仮定される。 受け口ヘッダは、重要ではない。 iv) 受け口関数 5) と開始キーワードの間 =end require 'optparse' $n_err = 0 $b_show = false $b_exist = false # コピー先にファイルがある場合のみマージ $old_mode = false # old_mode (関数本体として / の代わりに '{' を使う #2.0 def write_array( file, array ) array.each{ |line| file.write line } end class PortRenamer @@port_renamer_list = {} @@port_renamer_current = nil def initialize( old_port_name, new_port_name ) old_port_name = old_port_name.to_sym if new_port_name then new_port_name = new_port_name.to_sym end # p "port_renamer: #{old_port_name}" @@port_renamer_list[old_port_name] = self @@port_renamer_current = self @old_port_name = old_port_name @new_port_name = new_port_name @func_renamer_list = {} end def self.add_func( old_name, new_name ) # p "add_func: #{old_name}" if @@port_renamer_current == nil then STDERR.puts( "error: #{old_name}: specify -p before -f" ) n_err += 1 else @@port_renamer_current.add_func( old_name, new_name ) end end def add_func( old_name, new_name ) old_name = old_name.to_sym new_name = new_name.to_sym if @func_renamer_list[old_name] then STDERR.puts( "error: #{old_name}: duplicate in port #{@old_port_name}" ) $n_err += 1 elsif old_name == new_name then STDERR.puts( "error: #{old_name}: old and new are the same name" ) $n_err += 1 end @func_renamer_list[old_name] = new_name end def self.show if @@port_renamer_list then @@port_renamer_list.each { |pr,pc| pc.show } end end def self.get_list @@port_renamer_list end attr_accessor :old_port_name, :new_port_name, :func_renamer_list def show print "port: #{@old_port_name}" if @new_port_name then print " => #{@new_port_name}" end puts "" @func_renamer_list.each { |old,new| print " func: #{old} => #{new}\n" } end end class CDLContents #@head:: [string] #@preamble_comment:: [string] #@preamble_body:: [string] #@entry_port:: {ep_name=>CDLEntryPort} #@entry_port_array:: [CDLEntryPort] #@postamble_comment::[string] #@postamble_body:: [string] @@DELIMITERS = { # state => [ regexp_original, [previous states] , Regexp ] :HEAD => [ "#[]#", [] ], :PREAMBLE_COMMENT => [ "/* #[]#", [:HEAD] ], :PREAMBLE_BODY => [ " * #[]#", [:PREAMBLE_COMMENT] ], :ENTRY_COMMENT => [ "/* #[]# PORT_NAME", [:PREAMBLE_BODY, :ENTRY_FUNC_BODY, :ENTRY_FUNC_BODY2] ], :ENTRY_BODY => [ " * #[]#", [:ENTRY_COMMENT] ], :ENTRY_FUNC_COMMENT => [ "/* #[]# FUNC_NAME", [:ENTRY_BODY, :ENTRY_FUNC_BODY, :ENTRY_FUNC_BODY2] ], :POSTAMBLE_COMMENT => [ "/* #[]#", [:ENTRY_FUNC_BODY, :ENTRY_FUNC_BODY2, :PREAMBLE_BODY] ], :POSTAMBLE_BODY => [ " * #[]#", [:POSTAMBLE_COMMENT] ], :EOF => [ "#[]#", [:POSTAMBLE_BODY,:ENTRY_FUNC_BODY, :ENTRY_FUNC_BODY2, :PREAMBLE_BODY] ], } @@DELIMITERS_FUNC_BY_COMMENT = { :ENTRY_FUNC_BODY => [ " * #[]#", [:ENTRY_FUNC_COMMENT] ], :ENTRY_FUNC_BODY2 => [ " * #[/ENTRY_FUNC>]#", [:ENTRY_FUNC_COMMENT] ], # for Bug compatibility } @@DELIMITERS_FUNC_BY_BRACKET = { :ENTRY_FUNC_BODY => [ "\\\{", [:ENTRY_FUNC_COMMENT] ], } def self.rewrite_DELIMITERS delimiters delimiters.each{ |stat, stat_info| s = stat_info[0].gsub( /([\*\[\]])/, "\\\\\\1" ) # *, [, ] の前に \\ を挿入 s.gsub!( /\s*\w*_NAME/, "\\s*(\\w*)" ) # ..._NAME を (w*) に変更 s = "^" + s # ^ を先頭に挿入 stat_info[2] = Regexp.new( s ) # p stat_info[2] } end rewrite_DELIMITERS @@DELIMITERS rewrite_DELIMITERS @@DELIMITERS_FUNC_BY_COMMENT rewrite_DELIMITERS @@DELIMITERS_FUNC_BY_BRACKET #=== モードに従い DELIMITERS に DELIMITERS_FUNC_BY_* をマージする # mode :OLD_FUNC_BODY, :NEW_FUNC_BODY def self.merge_DELIMITERS mode case mode when :OLD_FUNC_BODY @@DELIMITERS.merge! @@DELIMITERS_FUNC_BY_COMMENT when :NEW_FUNC_BODY @@DELIMITERS.merge! @@DELIMITERS_FUNC_BY_BRACKET else raise "unknown mode" end end #all_contents:: [string,...] def initialize all_contents part = [] line_no = 0 stat = :HEAD arg = nil port_name = nil func_name = nil @entry_port = {} @entry_port_array = [] @head = [] @preamble_comment = [] @preamble_body = [] @postamble_comment = [] @postamble_body = [] (all_contents+[nil]).each{ |line| # nil: EOF line_no += 1 # p "L: #{line}" b_delim = false # デリミタキーワードの行 @@DELIMITERS.each { |next_stat,stat_info| next if next_stat == :HEAD || ( next_stat == :EOF && line != nil ) # #1002 tecsmerge の非受け口関数 (POSTAMBLE部) の行頭に '{' があるとエラーになる if (! $old_mode) && ( /^\{/ =~ line ) && ( stat == :PREAMBLE_BODY || stat == :POSTAMBLE_BODY ) p line + " next_stat=" + next_stat.to_s + "stat=" + stat.to_s next end # p "R: #{stat_info[0]}" if stat_info[2] =~ line || ( line == nil && next_stat == :EOF ) then # p "D: #{line}: #{stat}" b_delim = true found = false stat_info[1].each{ |prev_stat| if stat == prev_stat then found = true end } if ! found then STDERR.puts "error #{line_no}: unsuitable previous keyword" STDERR.puts "error #{line_no}: previous: \"#{@@DELIMITERS[stat][0]}\"" STDERR.puts "error #{line_no}: current: \"#{@@DELIMITERS[next_stat][0]}\"" expect = ""; delim = "" stat_info[1].each{ |prev_stat| expect = "#{expect+delim}\"#{@@DELIMITERS[prev_stat][0]}\""; delim = ", " } STDERR.puts "error #{line_no}: suitable previous: #{expect}" $n_err += 1 end case stat when :PREAMBLE_COMMENT, :ENTRY_COMMENT, :ENTRY_FUNC_COMMENT, :POSTAMBLE_COMMENT part << line end case stat # 前の状態 when :HEAD @head = part when :PREAMBLE_COMMENT @preamble_comment = part when :PREAMBLE_BODY @preamble_body = part when :ENTRY_COMMENT port_name = arg.to_sym @entry_port[ port_name ] = CDLEntryPort.new @entry_port_array << port_name @entry_port[ port_name ].entry_comment = part when :ENTRY_BODY @entry_port[ port_name ].entry_body = part when :ENTRY_FUNC_COMMENT func_name = arg.to_sym if @entry_port[ port_name ] then # nil なら既にエラー @entry_port[ port_name ].entry_func_comment[ func_name ] = part @entry_port[ port_name ].entry_func_array << func_name end when :ENTRY_FUNC_BODY, :ENTRY_FUNC_BODY2 if @entry_port[ port_name ] then # nil なら既にエラー @entry_port[ port_name ].entry_func_body[ func_name ] = part end when :POSTAMBLE_COMMENT @postamble_comment = part when :POSTAMBLE_BODY @postamble_body = part else raise "Unknown state #{stat}" end case next_stat when :PREAMBLE_COMMENT, :ENTRY_COMMENT, :ENTRY_FUNC_COMMENT, :POSTAMBLE_COMMENT part = [ line ] else part = [] end stat = next_stat arg = $1 # arg に取っておく # p stat, arg break end } if ! b_delim then part << line end } end def check template # template にないものをチェック @entry_port.each{ |port_name, entry_port| temp_entry_port = template.entry_port[port_name] if temp_entry_port == nil then STDERR.puts "info: #{port_name} is deleted port" next end # temp_entry_port.entry_func_body.each{ |f,b| p f } entry_port.entry_func_body.each{ |func_name, func_body| if temp_entry_port.entry_func_body[func_name] == nil then STDERR.puts "info: #{func_name} is deleted function" end } } end def rename renamed_entry_port = {} PortRenamer.get_list.each{ |pon,pr| # 対象受け口を捜す ep = @entry_port[pon] if ep == nil then STDERR.puts "warning: #{pon}: renaming port not found" next end # ポートの rename pnn = pr.new_port_name # 置換後の名前 if pnn then # 置換する名前があれば、登録しなおす renamed_entry_port[pnn] = @entry_port[pon] @entry_port.delete pon end # 指定された関数の置換 renamed_func_comment = {} renamed_func_body = {} pr.func_renamer_list.each{ |old,new| ofn = "#{pon}_#{old}".to_sym nfn = "#{pon}_#{new}".to_sym # p "fnn: #{ofn} #{nfn} #{pon}" if ep.entry_func_comment[ofn] == nil then STDERR.puts "warning: #{old}: renaming function not found" next end ep.entry_func_array.map! { |fn| # p fn, nfn, ofn, fn==ofn ? nfn : fn fn==ofn ? nfn : fn } renamed_func_comment[nfn] = ep.entry_func_comment[ofn] renamed_func_body[nfn] = ep.entry_func_body[ofn] ep.entry_func_comment.delete ofn ep.entry_func_body.delete ofn } ep.entry_func_comment.merge! renamed_func_comment ep.entry_func_body.merge! renamed_func_body # ポート名の変更による関数名の置換 renamed_func_comment = {} renamed_func_body = {} if pnn then ep.entry_func_array.map! { |ofn| nfn = ofn.to_s.sub( /#{pon.to_s}/, pnn.to_s ).to_sym # p "pnn: #{ofn} #{nfn} #{pon} #{pnn}" if nfn != ofn then renamed_func_comment[nfn] = ep.entry_func_comment[ofn] renamed_func_body[nfn] = ep.entry_func_body[ofn] ep.entry_func_comment.delete ofn ep.entry_func_body.delete ofn nfn else ofn end } ep.entry_func_comment.merge! renamed_func_comment ep.entry_func_body.merge! renamed_func_body # ep.entry_func_comment.each { |f,e| p "FF: #{f}" } # ep.entry_func_array.each { |f,e| p "FA: #{f}" } end } # p renamed_entry_port @entry_port.merge! renamed_entry_port end def merge src @head = src.head @preamble_body = src.preamble_body @postamble_body = src.postamble_body @entry_port_array.each{ |port_name| # p "merging #{port_name}" entry_port = @entry_port[ port_name ] src_entry_port = src.entry_port[port_name] if src_entry_port == nil then print "port merged: #{port_name}\n" next end entry_port.entry_body = src_entry_port.entry_body entry_port.entry_func_array.each{ |func_name| # p "merging #{func_name}" func_body = entry_port.entry_func_body[ func_name ] if src_entry_port.entry_func_body[func_name] == nil then print "func merged: #{func_name}\n" next end print "func remained: #{func_name}\n" # entry_port.entry_func_comment[func_name] = src_entry_port.entry_func_comment[func_name] entry_port.entry_func_body[func_name] = src_entry_port.entry_func_body[func_name] } } end def write file #2.0 file.write @head write_array( file, @head ) #2.0 file.write @preamble_comment write_array( file, @preamble_comment ) #2.0 file.write @preamble_body write_array( file, @preamble_body ) @entry_port_array.each{ |port_name| @entry_port[port_name].write file } #2.0 file.write @postamble_comment write_array( file, @postamble_comment ) #2.0 file.write @postamble_body write_array( file, @postamble_body ) end attr_accessor :head, :preamble_comment, :preamble_body, :entry_port, :postamble_body end class CDLEntryPort #@entry_comment:: [string] #@entry_body:: [string] #@entry_func_comment:: {ep_func_name=>string} #@entry_func_body:: {ep_func_name=>string} #@entry_func_array:: [string] def initialize @entry_comment = [] @entry_body = [] @entry_func_comment = {} @entry_func_body = {} @entry_func_array = [] end def write file #2.0 file.write @entry_comment write_array( file, @entry_comment ) #2.0 file.write @entry_body write_array( file, @entry_body ) @entry_func_array.each{ |fnm| # p @entry_func_comment[fnm][0] #2.0 file.write @entry_func_comment[fnm] write_array( file, @entry_func_comment[fnm] ) #2.0 file.write @entry_func_body[fnm] write_array( file, @entry_func_body[fnm] ) } end attr_accessor :entry_comment, :entry_body, :entry_func_comment, :entry_func_body, :entry_func_array end def merge( src_file, dst_dir ) unless src_file =~ /(.*)_templ(.[ch])$/ then STDERR.puts( "error: #{src_file}: not end with _templ.c/h" ) exit 1 end fname = "#{$1}#{$2}" dst_file = "#{dst_dir}/#{File.basename fname}" if FileTest.file?( dst_file ) then print( "merging #{src_file} to #{dst_file}\n" ) # dst_file の読込み begin dst = open( dst_file ) #2.0 set_encoding dst old_contents = dst.readlines rescue STDERR.puts "error: cannot open #{dst_file}" $n_err += 1 exit 1 ensure dst.close end old = CDLContents.new( old_contents ) # template の読込み begin src = open( src_file ) #2.0 set_encoding src new_contents = src.readlines rescue STDERR.puts "error: cannot open #{src_file}" $n_err += 1 exit 1 ensure src.close end templ = CDLContents.new( new_contents ) old.rename error_check dst_file, 1 old.check templ error_check dst_file, 2 templ.merge old error_check dst_file, 3 bkup_file = rename_dst( dst_file ) begin dst = open( dst_file, "w" ) #2.0 set_encoding dst templ.write dst ensure dst.close end elsif $b_exist == false then # src_file を dst_file へコピー begin src = File.open( src_file ) #2.0 set_encoding src contents = src.readlines rescue print "#{src_file}: fail to read\n" ensure src.close end begin dst = File.open( dst_file, "w" ) set_encoding dst #2.0 dst.write( contents ) contents.each{ |line| dst.print line } rescue print "#{dst_file}: fail to write\n" ensure dst.close end else print "info: #{dst_file} skipped\n" end end def error_check dst_file, level if $n_err > 0 then STDERR.puts "=== #{dst_file} not generated because of error ===" exit level end end #=== ファイルのエンコーディングを ASCII-8BIT に変更 # Ruby 1.9 以上の場合に変更 def set_encoding file if RUBY_VERSION >= "1.9.0" then file.set_encoding "ASCII-8BIT" end end #=== rename_dst # dst_file のバックアップファイル名を決定し、リネームする # 成功すれば、リネーム後のファイル名を返す # dst_file が存在しなければ(リネームは行われず)nil を返す。 def rename_dst( dst_file ) begin File.stat dst_file rescue STDERR.puts( "info: backup not generated for #{dst_file}" ) # なければ終わり return nil end i = 0 found = true while found == true begin i = i + 1 bkup_file = "#{dst_file}_#{i}" File.stat bkup_file rescue found = false end end begin File.rename( dst_file, bkup_file ) STDERR.puts( "info: backup generated for #{dst_file} to #{bkup_file}" ) return bkup_file rescue STDERR.puts( "error: fail to rename backup file: #{bkup_file}" ) exit 1 end return nil end ARGV.options {|parser| parser.banner =< 0 then STDERR.puts( "#{$n_err} errors" ) exit 1 end src = ARGV[0,ARGV.length-1] dst = ARGV[-1] begin stat = File.stat dst rescue STDERR.puts( "error: #{dst}: not found" ) exit 1 ensure if stat && ! stat.directory? then STDERR.puts( "error: #{dst}: not directory" ) exit 1 end end src.each { |s| begin stat = File.stat s rescue STDERR.puts( "error: #{s}: not found or cannot access" ) exit 1 end if stat.directory? then Dir.foreach(s) {|file| if file =~ /_templ.[ch]$/ then merge( "#{s}/#{file}", dst ) end } elsif stat.file? then merge( s, dst ) end }