私は2つの式パーサーを再帰的な下降と演算子の優先順位で実装しました。それらはObject Pascalで実装されています。ここでは再帰下降だ:パーサーの実装の比較:正しさの確認と(たぶん)最適化
function ParseFactor: PNode;
var
Temp: PNode;
begin
Result := ParsePrimary;
if t.Kind in [tkDoubleAsterisks] then begin
New(Temp);
Temp^.Kind := nkPower;
Temp^.LeftOperand := Result;
// power operator is right associative
Lexer.Get(t);
Temp^.RightOperand := ParseFactor();
Result := Temp;
end;
end;
function ParseTerm: PNode;
var
Temp: PNode;
begin
Result := ParseFactor;
while t.Kind in [tkAmpersand,tkAsterisk,tkSlash,tkDoubleLessThan,tkDoubleGreaterThan] do begin
New(Temp);
case t.Kind of
tkAmpersand : Temp^.Kind := nkAnd;
tkAsterisk : Temp^.Kind := nkMultiplication;
tkSlash : Temp^.Kind := nkDivision;
tkDoubleLessThan : Temp^.Kind := nkShiftLeft;
tkDoubleGreaterThan: Temp^.Kind := nkShiftRight;
end;
Temp^.LeftOperand := Result;
Lexer.Get(t);
Temp^.RightOperand := ParseFactor;
Result := Temp;
end;
end;
function ParseExpression: PNode;
var
Temp: PNode;
begin
Result := ParseTerm;
while t.Kind in [tkHorzBar,tkCaret,tkPlus,tkMinus] do begin
New(Temp);
case t.Kind of
tkHorzBar: Temp^.Kind := nkOr;
tkCaret : Temp^.Kind := nkXor;
tkPlus : Temp^.Kind := nkAddition;
tkMinus : Temp^.Kind := nkSubstraction;
end;
Temp^.LeftOperand := Result;
Lexer.Get(t);
Temp^.RightOperand := ParseTerm;
Result := Temp;
end;
end;
、ここでは、演算子の優先順位は次のとおりです。
function GetTokenPrecedence(const Kind: TTokenKind): Integer; inline;
begin
case Kind of
tkHorzBar,tkCaret,tkPlus,tkMinus:
Result := 1;
tkAmpersand,tkAsterisk,tkSlash,tkDoubleLessThan,tkDoubleGreaterThan:
Result := 2;
tkDoubleAsterisks:
Result := 3;
else
Result := -1;
end;
end;
function IsRightAssociative(const Kind: TTokenKind): Boolean; inline;
begin
Result := Kind in [tkDoubleAsterisks];
end;
function ParseBinaryRHSExpression(LHS: PNode; const CurrentPrecedence: Integer): PNode;
var
Op: TTokenKind;
RHS: PNode;
begin
while GetTokenPrecedence(t.Kind) >= CurrentPrecedence do begin
Op := t.Kind;
Lexer.Get(t);
RHS := ParsePrimary;
while (GetTokenPrecedence(t.Kind) > GetTokenPrecedence(Op))
or (IsRightAssociative(t.Kind) and (GetTokenPrecedence(t.Kind) >= GetTokenPrecedence(Op)))
do begin
RHS := ParseBinaryRHSExpression(RHS,GetTokenPrecedence(t.Kind));
end;
New(Result);
case Op of
tkHorzBar : Result^.Kind := nkOr;
tkCaret : Result^.Kind := nkXor;
tkPlus : Result^.Kind := nkAddition;
tkMinus : Result^.Kind := nkSubstraction;
tkAmpersand : Result^.Kind := nkAnd;
tkAsterisk : Result^.Kind := nkMultiplication;
tkSlash : Result^.Kind := nkDivision;
tkDoubleLessThan : Result^.Kind := nkShiftLeft;
tkDoubleGreaterThan: Result^.Kind := nkShiftRight;
tkDoubleAsterisks : Result^.Kind := nkPower;
end;
Result^.LeftOperand := LHS;
Result^.RightOperand := RHS;
LHS := Result;
end;
Result := LHS;
end;
function ParseExpression: PNode;
begin
Result := ParsePrimary;
if Assigned(Result) then begin
Result := ParseBinaryRHSExpression(Result,0);
end;
end;
これらは両者の唯一の本質的な違いです。いくつかの簡単なテストでは、両方が正しく解析されているようです。しかし、実際に実装するのは初めてですので、オペレータの優先順位の実装についてはわかりません。と私を悩ます驚くべき事実、それは再帰的な降下バージョン(それは1.5回以上かかる)よりも遅く実行されます!私のコンパイラクラスと私が読んだすべての記事では、演算子の優先順位の解析は、関数呼び出しが少なくて済み、再帰的な降下よりも効率的でなければならないと述べています。私は演算子の優先順位と右アソシエーショニティテストを得るためにインラインdの追加機能を持っていますが、これはあまり役に立たないようです。私が間違っていたかどうか教えてください。特定のことを明確にするように気をつけてください。
'多くの演算子を持っていると演算子の優先順位が速くなるはずです。 一般的なプログラミング言語は、演算子のオーバーロードをサポートしていても通常は多くの演算子も持っていません。おおよその数を「たくさん」と見なすことはできますか? – LeleDumbo
いいえ、副次的な影響が多すぎます(あなたのレクサーをどのようにコーディングしたか、あなたの言語の文字列管理のオーバーヘッド、...)。それを構築して調整し、それが十分であるかどうかを判断する必要があります。ここでの私の主張は、演算子の優先順位はパフォーマンスの面で興味深いものを買うことはないということです。だから、それは問題の価値がない。あなたは実験を行いましたので、答えに慣れ親しむことができます。 –
私は「楽しい」(楽しいこと:自己研究、脳の科学的側面が何とか活性化される)のためだけにやってくれました。両方のパーサーが同じレクサーを使用しているので、副次的な影響は問題ではありません。 1つが遅くなったら、もう1つはそれを感じるはずです。パーサーコードは文字列をまったく扱わない。 – LeleDumbo