バイナリ化

パースされたものは次にバイナリ化するのだが、その前にも少し操作がある。

オフセットの計算

パースされたものは各々下記のようにオフセット計算される。

# $raPartsにパースされた結果の配列の参照が格納されている
my $ofs = 0;
for my $raPart (@{$raParts}) {
   $raPart->{ofs} = $ofs;
   my $type = $raPart->{type};
   if    ( $type == TYPE_PLAIN    ) { $ofs += 8;  }
   elsif ( $type == TYPE_REPLACE  ) { $ofs += 12; }
   elsif ( $type == TYPE_IF       ) { $ofs += 24; }
   elsif ( $type == TYPE_ELSE     ) { $ofs += 12; }
   elsif ( $type == TYPE_LOOP     ) { $ofs += 12; }
   elsif ( $type == TYPE_QSA      ) { $ofs += 8;  }
   elsif ( $type == TYPE_LB       ) { $ofs += 4;  }
   elsif ( $type == TYPE_RB       ) { $ofs += 4;  }
   elsif ( $type == TYPE_END      ) { $ofs += 4;  }
}

このオフセットは、XSのときにテンプレートを読み込む時に用いられる。次に、IF/ELSE/LOOPでの飛び先を、配列の番号からオフセットに変更する処理が行われる。

次に文字列は、テンプレートの後ろの方に移動させる。文字列が入っていたところは、その文字列の位置を指すように変更する。例えば、プレーンテキストの場合。

$raPart->{text} = useStringPos(\$strBuf, \%strPos, $raPart->{text});

$raPart->{text}には最初に文字列が入っているが、useStringPos関数を実行した後は、文字列の位置を示すようになる。useStringPos関数は次のような処理を行っている。

sub useStringPos {
   my ($rStrBuf, $rhStrPos, $str) = @_;

   # 同じ文字列がすでにあったら、そこを参照するようにする
   if (exists($rhStrPos->{$str})) {
      return($rhStrPos->{$str});
   }
 
   # 新たな文字列だったら、$rStrBufの後ろに文字列を登録、その位置を返す
   my $newPos = length(${$rStrBuf});
   $rhStrPos->{$str} = $newPos;
   ${$rStrBuf} .= ($str . chr(0));
   return($newPos);
}

ここで、終了。後は、パース結果の配列をpack関数でバイナリ化する。
ファイルを書き込む時は「パース結果配列のバイナリ化した時のサイズ+パース結果配列のバイナリ化部分+文字列」の形でバイナリテンプレートとしてファイル出力される。
以上がテンプレートコンパイル処理。