#
#	MkProdDef.awk
#
#	演出制御データ作成スクリプト
#
#	* Thu Jul 04 01:04:19 JST 2013 Naoyuki Sawa
#	- 1stリリース。
#	- 使い方は、MkProdDef.batの[使い方]を参照してください。
#	  本スクリプトを直接実行することも可能ですが、MkProdDef.batを利用する方が楽です。
#	* Fri Sep 13 20:13:42 JST 2013 Naoyuki Sawa
#	- ProdSet_Flag_Multiの削除,ProdSet_Flag_Immediateの追加に追従しました。
#	  clipprod.h,clipprod.cの同日のコメントを参照してください。
#

#--- エラー終了 ---
function abort(msg) {
	#エラーメッセージを表示します。
	print "### " msg > "/dev/stderr"
	#終了処理を行わないフラグをセットします。
	_abort = 1
	#結果コード1で終了します。
	exit 1
}

#--- 開始処理 ---
BEGIN {
	#フィールドセパレータをタブに設定します。(CSV入力)
	FS = "\t"
}

#--- レコード処理 ---
{
	#各要素の中の、空白を削除します。
	gsub(/ /, "")

	#「//」以降を、コメントとして切り捨てます。
	sub(/\/\/.*/, "")

	#末尾にある、空要素を削除します。
	sub(/\t+$/, "")

	#「#」で始まる行は、疑似命令として処理します。
	if(/^#/) {
		#疑似命令行は、二列目以降の要素があってはいけません。
		if(NF != 1) { abort(FILENAME "(" FNR ")" "疑似命令行は、二列目以降の要素があってはいけません。") }
		#疑似命令行を、名前と引数に分離します。
		$0 = gensub(/^#([_[:alpha:]][_[:alnum:]]*)\((.+)\)$/, "\\1" FS "\\2", 1)
		if(NF != 2) { abort(FILENAME "(" FNR ")" "疑似命令行を、名前と引数に分離する際に、エラーが発生しました。") }
		sDirective = $1         #疑似命令の名前
		n1 = split($2, a1, ",") #疑似命令の引数
		#疑似命令の名前によって…
		#「#Include(sFileName)」
		if(sDirective == "Include") {
			#第一引数は、インクルードファイルでなくてはなりません。
			if((n1 != 1) || (a1[1] == "")) { abort(FILENAME "(" FNR ")" "第一引数は、インクルードファイルでなくてはなりません。") }
			#インクルードファイルを追加します。
			aInclude[length(aInclude) + 1] = a1[1]
		#「#Trigger(iTrigger,sTriggerName[,(Im|Wt)=*[,(Im|Wt)=*]])」
		} else if(sDirective == "Trigger") {
			#第一引数は、トリガ番号でなくてはなりません。
			if((n1 < 1) || (a1[1] !~ /^(0|-?[1-9][0-9]*)$/)) { abort(FILENAME "(" FNR ")" "第一引数は、トリガ番号でなくてはなりません。") }
			iTrigger = a1[1] + 0
			if((iTrigger < 0) || (iTrigger > 255)) { abort(FILENAME "(" FNR ")" "トリガ番号が不正です。") }
			#第二引数は、トリガ名でなくてはなりません。
			# - トリガ名は、Cシンボルでなくても構わないこととしました。(数字で始まっても良い)
			#   慣例上の都合で、「1on」「3off」等、数字で始まるトリガ名を使用したいからです。
			if((n1 < 2) || (a1[2] == "")) { abort(FILENAME "(" FNR ")" "第二引数は、トリガ名でなくてはなりません。") }
			TBL_TriggerName[a1[2]] = iTrigger #トリガ名⇒トリガ番号変換テーブルに登録します。
			#第三引数〜は、フラグのデフォルト値です。
			for(i = 3; i <= n1; i++) {
				#フラグ名と、値を分離します。
				n2 = split(a1[i], a2, "=")
				if(n2 != 2) { abort(FILENAME "(" FNR ")" "フラグ名と、値を分離する際に、エラーが発生しました。") }
				#値を、数値に変換します。
				if(a2[2] !~ /^(0|-?[1-9][0-9]*)$/) { abort(FILENAME "(" FNR ")" "値を、数値に変換する際に、エラーが発生しました。") }
				a2[2] = a2[2] + 0
				#フラグ名が、"Im"ならば…	ProdSet_Flag_Immediate
				if(a2[1] == "Im") {
					if((a2[2] < 0) || (a2[2] > 1)) { abort(FILENAME "(" FNR ")" "フラグのデフォルト値が不正です。") }
					aDefault_Im[iTrigger] = a2[2]
				#フラグ名が、"Wt"ならば…	ProdSet.Warrant
				} else if(a2[1] == "Wt") {
					if((a2[2] < -1) || (a2[2] > 65535)) { abort(FILENAME "(" FNR ")" "フラグのデフォルト値が不正です。") }
					aDefault_Wt[iTrigger] = a2[2]
				} else {
					abort(FILENAME "(" FNR ")" "フラグ名が不正です。")
				}
			}
		#「#Delay((En|Rp)=*[,(En|Rp)=*])」
		} else if(sDirective == "Delay") {
			#第一引数〜は、フラグのデフォルト値です。
			for(i = 1; i <= n1; i++) {
				#フラグ名と、値を分離します。
				n2 = split(a1[i], a2, "=")
				if(n2 != 2) { abort(FILENAME "(" FNR ")" "フラグ名と、値を分離する際に、エラーが発生しました。") }
				#値を、数値に変換します。
				if(a2[2] !~ /^(0|-?[1-9][0-9]*)$/) { abort(FILENAME "(" FNR ")" "値を、数値に変換する際に、エラーが発生しました。") }
				a2[2] = a2[2] + 0
				#フラグ名が、"En"ならば…	ProdOrd_Flag_Ensure
				if(a2[1] == "En") {
					if((a2[2] < 0) || (a2[2] > 1)) { abort(FILENAME "(" FNR ")" "フラグのデフォルト値が不正です。") }
					sDefault_En = a2[2]
				#フラグ名が、"Rp"ならば…	ProdOrd.Repeat
				} else if(a2[1] == "Rp") {
					if((a2[2] < -1) || (a2[2] > 255)) { abort(FILENAME "(" FNR ")" "フラグのデフォルト値が不正です。") }
					sDefault_Rp = a2[2]
				} else {
					abort(FILENAME "(" FNR ")" "フラグ名が不正です。")
				}
			}
		#「#Device(iDevice,sPrefix[,Nr=*])」
		} else if(sDirective == "Device") {
			#第一引数は、デバイス番号でなくてはなりません。
			if((n1 < 1) || (a1[1] !~ /^(0|-?[1-9][0-9]*)$/)) { abort(FILENAME "(" FNR ")" "第一引数は、デバイス番号でなくてはなりません。") }
			iDevice = a1[1] + 0
			if((iDevice < 0) || (iDevice > 255)) { abort(FILENAME "(" FNR ")" "デバイス番号が不正です。") }
			#第二引数は、プレフィクスでなくてはなりません。(空文字列,又は,シンボルであること)
			if((n1 < 2) || (a1[2] !~ /^(|[_[:alpha:]][_[:alnum:]]*)$/)) { abort(FILENAME "(" FNR ")" "第二引数は、プレフィクスでなくてはなりません。") }
			aPrefix[iDevice] = a1[2]
			#第三引数〜は、フラグのデフォルト値です。
			for(i = 3; i <= n1; i++) {
				#フラグ名と、値を分離します。
				n2 = split(a1[i], a2, "=")
				if(n2 != 2) { abort(FILENAME "(" FNR ")" "フラグ名と、値を分離する際に、エラーが発生しました。") }
				#値を、数値に変換します。
				if(a2[2] !~ /^(0|-?[1-9][0-9]*)$/) { abort(FILENAME "(" FNR ")" "値を、数値に変換する際に、エラーが発生しました。") }
				a2[2] = a2[2] + 0
				#フラグ名が、"Nr"ならば…	ProdSet_Flag_Nr
				if(a2[1] == "Nr") {
					if((a2[2] < 0) || (a2[2] > 1)) { abort(FILENAME "(" FNR ")" "フラグのデフォルト値が不正です。") }
					aDefault_Nr[iDevice] = a2[2]
				} else {
					abort(FILENAME "(" FNR ")" "フラグ名が不正です。")
				}
			}
		} else {
			abort(FILENAME "(" FNR ")" "疑似命令が不正です。")
		}
		next
	}

	#一列目の要素は、その内容にかかわらず、コメントと見なします。
	#二列目以降の要素が無ければ、空レコードと見なし、飛ばします。
	if(NF < 2) { next }

	#処理する行の各行について、元ファイル行との対応を記録します。
	LineMax++
	aFileName[LineMax] = FILENAME
	aFnr     [LineMax] = FNR

	#フィールドを取得します。
	aProdSet  [LineMax] = $2 #演出セット名
	aTrigger  [LineMax] = $3 #トリガ名[,Im=*][,Wt=*]
	aDelay    [LineMax] = $4 #ディレイ[,En=*][,Rp=*]
	aDataCount[LineMax] = 0  #データ数
	for(iDevice = 0; iDevice <= 255; iDevice++) {
		if( (5+iDevice) >  NF) { break }
		if($(5+iDevice) != "") {
			aDataCount[LineMax]++                                  #データ数
			aDevice   [LineMax,aDataCount[LineMax]] = iDevice      #デバイス
			aData     [LineMax,aDataCount[LineMax]] = $(5+iDevice) #データ[,Nr=*]
		}
	}

	#トリガ名から、フラグを分離します。
	if(aTrigger[LineMax] != "") {
		n1 = split(aTrigger[LineMax], a1, ",")
		if((n1 < 1) || !(a1[1] in TBL_TriggerName)) { abort(FILENAME "(" FNR ")" "トリガ名から、フラグを分離する際に、エラーが発生しました。") }
		iTrigger = TBL_TriggerName[a1[1]] #トリガ名⇒トリガ番号変換
		if((iTrigger < 0) || (iTrigger > 255)) { abort(FILENAME "(" FNR ")" "トリガ番号が不正です。") }
		aTrigger   [LineMax] = iTrigger
		aTrigger_Im[LineMax] = aDefault_Im[iTrigger] + 0 #デフォルト値
		aTrigger_Wt[LineMax] = aDefault_Wt[iTrigger] + 0 #デフォルト値
		#各フラグについて…
		for(i = 2; i <= n1; i++) {
			#フラグ名と、値を分離します。
			n2 = split(a1[i], a2, "=")
			if(n2 != 2) { abort(FILENAME "(" FNR ")" "フラグ名と、値を分離する際に、エラーが発生しました。") }
			#値を、数値に変換します。
			if(a2[2] !~ /^(0|-?[1-9][0-9]*)$/) { abort(FILENAME "(" FNR ")" "値を、数値に変換する際に、エラーが発生しました。") }
			a2[2] = a2[2] + 0
			#フラグ名が、"Im"ならば…	ProdSet_Flag_Immediate
			if(a2[1] == "Im") {
				if((a2[2] < 0) || (a2[2] > 1)) { abort(FILENAME "(" FNR ")" "フラグの値が不正です。") }
				aTrigger_Im[LineMax] = a2[2]
			#フラグ名が、"Wt"ならば…	ProdSet.Warrant
			} else if(a2[1] == "Wt") {
				if((a2[2] < -1) || (a2[2] > 65535)) { abort(FILENAME "(" FNR ")" "フラグの値が不正です。") }
				aTrigger_Wt[LineMax] = a2[2]
			} else {
				abort(FILENAME "(" FNR ")" "フラグ名が不正です。")
			}
		}
	}

	#ディレイから、フラグを分離します。
	if(aDelay[LineMax] != "") {
		n1 = split(aDelay[LineMax], a1, ",")
		if((n1 < 1) || (a1[1] !~ /^(0|-?[1-9][0-9]*)$/)) { abort(FILENAME "(" FNR ")" "ディレイから、フラグを分離する際に、エラーが発生しました。") }
		iDelay = a1[1] + 0
		if((iDelay < -1) || (iDelay > 65535)) { abort(FILENAME "(" FNR ")" "ディレイが不正です。") }
		aDelay   [LineMax] = iDelay
		aDelay_En[LineMax] = sDefault_En + 0 #デフォルト値
		aDelay_Rp[LineMax] = sDefault_Rp + 0 #デフォルト値
		#各フラグについて…
		for(i = 2; i <= n1; i++) {
			#フラグ名と、値を分離します。
			n2 = split(a1[i], a2, "=")
			if(n2 != 2) { abort(FILENAME "(" FNR ")" "フラグ名と、値を分離する際に、エラーが発生しました。") }
			#値を、数値に変換します。
			if(a2[2] !~ /^(0|-?[1-9][0-9]*)$/) { abort(FILENAME "(" FNR ")" "値を、数値に変換する際に、エラーが発生しました。") }
			a2[2] = a2[2] + 0
			#フラグ名が、"En"ならば…	ProdOrd_Flag_Ensure
			if(a2[1] == "En") {
				if((a2[2] < 0) || (a2[2] > 1)) { abort(FILENAME "(" FNR ")" "フラグの値が不正です。") }
				aDelay_En[LineMax] = a2[2]
			#フラグ名が、"Rp"ならば…	ProdOrd.Repeat
			} else if(a2[1] == "Rp") {
				if((a2[2] < -1) || (a2[2] > 255)) { abort(FILENAME "(" FNR ")" "フラグの値が不正です。") }
				aDelay_Rp[LineMax] = a2[2]
			} else {
				abort(FILENAME "(" FNR ")" "フラグ名が不正です。")
			}
		}
	}

	#各データについて…
	for(iData = 1; iData <= aDataCount[LineMax]; iData++) {
		iDevice = aDevice[LineMax,iData]
		#データから、フラグを分離します。
		n1 = split(aData[LineMax,iData], a1, ",")
		if((n1 < 1) || (a1[1] == "")) { abort(FILENAME "(" FNR ")" "データから、フラグを分離する際に、エラーが発生しました。") }
		sData = a1[1]
		if(sData ~ /^(0|-?[1-9][0-9]*)$/) {
			#データが数値ならば、そのまま使用します。
		} else if(sData ~ /^[_[:alpha:]][_[:alnum:]]*$/) {
			#データがシンボルならば、プレフィクスを付与します。
			sData = aPrefix[iDevice] sData
		} else {
			abort(FILENAME "(" FNR ")" "データが不正です。")
		}
		aData   [LineMax,iData] = sData
		aData_Nr[LineMax,iData] = aDefault_Nr[iDevice] + 0 #デフォルト値
		#各フラグについて…
		for(i = 2; i <= n1; i++) {
			#フラグ名と、値を分離します。
			n2 = split(a1[i], a2, "=")
			if(n2 != 2) { abort(FILENAME "(" FNR ")" "フラグ名と、値を分離する際に、エラーが発生しました。") }
			#値を、数値に変換します。
			if(a2[2] !~ /^(0|-?[1-9][0-9]*)$/) { abort(FILENAME "(" FNR ")" "値を、数値に変換する際に、エラーが発生しました。") }
			a2[2] = a2[2] + 0
			#フラグ名が、"Nr"ならば…	ProdSet_Flag_Nr
			if(a2[1] == "Nr") {
				if((a2[2] < 0) || (a2[2] > 1)) { abort(FILENAME "(" FNR ")" "フラグの値が不正です。") }
				aData_Nr[LineMax,iData] = a2[2]
			} else {
				abort(FILENAME "(" FNR ")" "フラグ名が不正です。")
			}
		}
	}
}

#--- 終了処理 ---
END {
	#エラー終了ならば、終了処理を行いません。
	if(_abort) { exit } #直前のexitでセットされた、終了コードを返します。

	#最初の演出セットの開始行を検索します。(必須)
	iLine = 1
	while(aProdSet[iLine] == "") {
		if((iLine > LineMax) || (aTrigger[iLine] != "") || (aDelay[iLine] != "") || (aDataCount[iLine] != 0)) { abort(aFileName[iLine] "(" aFnr[iLine] ")" "最初の演出セットの開始行を検索する際に、エラーが発生しました。") }
		iLine++
	}

	#演出セットリスト,演出順序リスト,及び,演出コマンドリストの、要素数を初期化します。
	nProdSetList = 1 #この後のCソース出力時に、[0]は無効要素として明示的に出力します。有効要素は[1]から登録します。
	nProdOrdList = 1 #この後のCソース出力時に、[0]は無効要素として明示的に出力します。有効要素は[1]から登録します。
	nProdCmdList = 1 #この後のCソース出力時に、[0]は無効要素として明示的に出力します。有効要素は[1]から登録します。

	#--- Cヘッダ出力 ---
	print "//{{HEADER"
	#各行について…
	delete fProdSet #演出セットが多重定義されていないことを確認するための配列
	do {
		#演出セットの開始行であることを確認します。
		if(aProdSet[iLine] == "") { abort(aFileName[iLine] "(" aFnr[iLine] ")" "演出セットの開始行であることを確認する際に、エラーが発生しました。") }
		iLineStart = iLine
		#演出セットが多重定義されていないことを確認します。
		if(aProdSet[iLine] in fProdSet) { abort(aFileName[iLine] "(" aFnr[iLine] ")" "演出セットが多重定義されています。") }
		fProdSet[aProdSet[iLine]] = 1
		#演出セットの終了行を検索します。
		do { iLine++ } while((iLine <= LineMax) && (aProdSet[iLine] == ""))
		iLineEnd   = iLine - 1
		#演出セットリストを組み立て、インデクスを取得します。
		i = Build_ProdSetList(iLineStart, iLineEnd)
		#演出セットIDを定義します。
		print "#define ProdSet_" aProdSet[iLineStart] " " i
	} while(iLine <= LineMax)
	#演出セットIDの(限界値+1)を定義します。
	print "#define ProdSet_SIZE " nProdSetList
	print "extern const struct _ProdSet TBL_ProdSet[ProdSet_SIZE];"
	print "//}}HEADER"

	#--- Cソース出力 ---
	print "//{{SOURCE"
	for(i = 1; i <= length(aInclude); i++) { print "#include \"" aInclude[i] "\"" }
	print "static const struct _ProdCmd TBL_ProdCmd[" nProdCmdList "]={{0},\n" sProdCmdList "};" #[0]は無効要素として明示的に出力します。
	print "static const struct _ProdOrd TBL_ProdOrd[" nProdOrdList "]={{0},\n" sProdOrdList "};" #[0]は無効要素として明示的に出力します。
	print        "const struct _ProdSet TBL_ProdSet[" nProdSetList "]={{0},\n" sProdSetList "};" #[0]は無効要素として明示的に出力します。
	print "//}}SOURCE"
}

#--- 演出セットリストを組み立てる ---
function Build_ProdSetList(iLine, LineMax,
	iLineStart, iLineEnd, s, n, i, f) { #ローカル変数

	#最初のトリガの開始行を検索します。(必須)
	while(aTrigger[iLine] == "") {
		if((iLine > LineMax) || (aDelay[iLine] != "") || (aDataCount[iLine] != 0)) { abort(aFileName[iLine] "(" aFnr[iLine] ")" "最初のトリガの開始行を検索する際に、エラーが発生しました。") }
		iLine++
	}

	#各行について…
	delete fTrigger #トリガが多重定義されていないことを確認するための配列
	do {
		#トリガの開始行であることを確認します。
		if(aTrigger[iLine] == "") { abort(aFileName[iLine] "(" aFnr[iLine] ")" "トリガの開始行であることを確認する際に、エラーが発生しました。") }
		iLineStart = iLine
		#トリガが多重定義されていないことを確認します。
		if(aTrigger[iLine] in fTrigger) { abort(aFileName[iLine] "(" aFnr[iLine] ")" "トリガが多重定義されています。") }
		fTrigger[aTrigger[iLine]] = 1
		#トリガの終了行を検索します。
		do { iLine++ } while((iLine <= LineMax) && (aTrigger[iLine] == ""))
		iLineEnd   = iLine - 1
		#演出順序リストを組み立て、インデクスを取得します。
		i = Build_ProdOrdList(iLineStart, iLineEnd)
		#フラグを合成します。
		f = 0
		if(aTrigger_Im[iLineStart] != 0) { f += (2^0) } #Im
		if(iLineEnd == LineMax)          { f += (2^7) } #End
		#演出セットを追加します。
		s = s "{" f                       #フラグ
		s = s "," aTrigger   [iLineStart] #トリガ
		s = s "," aTrigger_Wt[iLineStart] #Wt
		s = s "," "TBL_ProdOrd+" i        #演出順序リスト
		s = s "},"
		n++
	} while(iLine <= LineMax)

	#演出セットリストを、重複無く登録します。
	if(!(s in aProdSetList)) {
		aProdSetList[s] = nProdSetList
		                  nProdSetList += n
		sProdSetList    = sProdSetList    s "//" aProdSetList[s] "\n"
	}

	#演出セットリストのインデクスを返します。
	return aProdSetList[s]
}

#--- 演出順序リストを組み立てる ---
function Build_ProdOrdList(iLine, LineMax,
	iLineStart, iLineEnd, s, n, i, f) { #ローカル変数

	#最初のディレイの開始行を検索します。(必須)
	while(aDelay[iLine] == "") {
		if((iLine > LineMax) || (aDataCount[iLine] != 0)) { abort(aFileName[iLine] "(" aFnr[iLine] ")" "最初のディレイの開始行を検索する際に、エラーが発生しました。") }
		iLine++
	}

	#各行について…
	do {
		#ディレイの開始行であることを確認します。
		if(aDelay[iLine] == "") { abort(aFileName[iLine] "(" aFnr[iLine] ")" "ディレイの開始行であることを確認する際に、エラーが発生しました。") }
		iLineStart = iLine
		#ディレイの終了行を検索します。
		do { iLine++ } while((iLine <= LineMax) && (aDelay[iLine] == ""))
		iLineEnd   = iLine - 1
		#演出コマンドリストを組み立て、インデクスを取得します。
		i = Build_ProdCmdList(iLineStart, iLineEnd)
		#フラグを合成します。
		f = 0
		if(aDelay_En[iLineStart] != 0) { f += (2^0) } #En
		if(iLineEnd == LineMax)        { f += (2^7) } #End
		#演出順序を追加します。
		s = s "{" f                     #フラグ
		s = s "," aDelay_Rp[iLineStart] #Rp
		s = s "," aDelay   [iLineStart] #ディレイ
		s = s "," "TBL_ProdCmd+" i      #演出コマンドリスト
		s = s "},"
		n++
	} while(iLine <= LineMax)

	#演出順序リストを、重複無く登録します。
	if(!(s in aProdOrdList)) {
		aProdOrdList[s] = nProdOrdList
		                  nProdOrdList += n
		sProdOrdList    = sProdOrdList    s "//" aProdOrdList[s] "\n"
	}

	#演出順序リストのインデクスを返します。
	return aProdOrdList[s]
}

#--- 演出コマンドリストを組み立てる ---
function Build_ProdCmdList(iLine, LineMax,
	iLineStart, iLineEnd, s, n, i, f) { #ローカル変数

	#最初のデータの開始行を検索します。(必須)
	while(aDataCount[iLine] == 0) {
		if(iLine > LineMax) { abort(aFileName[iLine] "(" aFnr[iLine] ")" "最初のデータの開始行を検索する際に、エラーが発生しましたq。") }
		iLine++
	}

	#各行について…
	do {
		#データが有ることを確認します。
		if(aDataCount[iLine] == 0) { abort(aFileName[iLine] "(" aFnr[iLine] ")" "データが有ることを確認する際に、エラーが発生しました。") }
		#各データについて…
		for(i = 1; i <= aDataCount[iLine]; i++) {
			#フラグを合成します。
			f = 0
			if(aData_Nr[iLine,i] != 0)                         { f += (2^0) } #Nr
			if((iLine == LineMax) && (i == aDataCount[iLine])) { f += (2^7) } #End
			#演出コマンドを追加します。
			s = s "{" f                #フラグ
			s = s "," aDevice[iLine,i] #デバイス
			s = s "," aData  [iLine,i] #データ
			s = s "},"
			n++
		}
		#次の行へ進めます。
		iLine++
	} while(iLine <= LineMax)

	#演出コマンドリストを、重複無く登録します。
	if(!(s in aProdCmdList)) {
		aProdCmdList[s] = nProdCmdList
		                  nProdCmdList += n
		sProdCmdList    = sProdCmdList    s "//" aProdCmdList[s] "\n"
	}

	#演出コマンドリストのインデクスを返します。
	return aProdCmdList[s]
}
