これは、これまでパーサー/レクサーを作成したことがない人からの初歩的な質問です。
PHP で CSS のトークナイザー/パーサーを作成しています (「OMG、なぜ PHP で?」と繰り返さないでください)。構文は W3C によってここ (CSS2.1)とここ (CSS3、ドラフト)にきちんと書き留められています。
これは 21 の可能なトークンのリストであり、すべて (2 つを除く) を静的文字列として表すことはできません。
私の現在のアプローチは、21 個のパターンを含む配列を何度もループしてif (preg_match())
、ソース文字列の一致を一致ごとに減らしていくというものです。原則として、これは非常にうまく機能します。ただし、1000 行の CSS 文字列の場合、これには 2 ~ 8 秒かかります。これは、私のプロジェクトには多すぎます。
今、私は他のパーサーがどのように CSS をトークン化して数秒で解析するのか頭を悩ませています。OK、Cは常に PHP よりも高速ですが、それにもかかわらず、明らかなD'Oh!はありますか? 私が陥ったのですか?
残りの文字列の最初の文字として「@」、「#」、または「"」をチェックし、関連する正規表現のみを適用するなど、いくつかの最適化を行いましたが、これによりパフォーマンスが大幅に向上することはありませんでした。
これまでの私のコード(スニペット):
$TOKENS = array(
'IDENT' => '...regexp...',
'ATKEYWORD' => '@...regexp...',
'String' => '"...regexp..."|\'...regexp...\'',
//...
);
$string = '...CSS source string...';
$stream = array();
// we reduce $string token by token
while ($string != '') {
$string = ltrim($string, " \t\r\n\f"); // unconsumed whitespace at the
// start is insignificant but doing a trim reduces exec time by 25%
$matches = array();
// loop through all possible tokens
foreach ($TOKENS as $t => $p) {
// The '&' is used as delimiter, because it isn't used anywhere in
// the token regexps
if (preg_match('&^'.$p.'&Su', $string, $matches)) {
$stream[] = array($t, $matches[0]);
$string = substr($string, strlen($matches[0]));
// Yay! We found one that matches!
continue 2;
}
}
// if we come here, we have a syntax error and handle it somehow
}
// result: an array $stream consisting of arrays with
// 0 => type of token
// 1 => token content