tl; drシャンティングヤードアルゴリズムの実装に関数を組み込む簡単な方法は何ですか?
function(arg1, arg2, arg3)
のような式のみが許可されている場合(function
は組み込み関数です)、function
を演算子のように扱うことができるため、非常に簡単です。ただし、ユーザーがf = function
のような独自の関数を定義し、f(arg1, arg2, arg3)
を呼び出す場合を考えてみます。この場合、進行中のトークン((arg1, arg2, arg3)
)が実際には関数呼び出しであり、タプルの構築だけではないことを確認するために、コンパイル時にf
の型を検出するために、厳密に型指定されたASTが必要です。
さらに悪いことに、f
がユーザー定義のnullary関数である(f)()
を検討してください。次に、f
に到達すると、それが関数であることがわかっていても、次のトークンは)
になり、これは有効な関数呼び出しの開始ではありません。 (l[i])()
についてはどうですか、ここでl
は関数のリストです。
最も一般的なレベルでは、[expression], "(", [expression], ")"
のようなステートメントがある場合、関数を呼び出していることがわかります。ただし、ASTを実装せずにこれを確認する方法はわかりません(簡単にするために、私はそうしません)。
すべての演算子と「ブラケット」トークンのリストを保存し、想定される関数呼び出しで「(」に到達したら、最後の非ブラケットトークンが演算子であったかどうかを確認します。それが演算子の場合、「 ( "は5 * (3 - 8)
のような部分式を表します。演算子ではなかった場合、"( "は関数呼び出しを表します。ただし、このメソッドは簡単に壊れるように感じます。たとえば、ある演算子が$
は「単項左結合」だったので、(expression $)(args)
は有効でしたか?$
を特別にチェックしない限り、アルゴリズムは失敗します。function \* comment *\ (args)
のように、関数と関数呼び出しの間にコメントがある場合はどうなりますか? ?またはさらに悪いことに、次のようなもの
function \\ lol the last token in this comment is an operator +
(args)
これらはハンドラーを実装する多くの特別なケースを必要とし、それを行うより良い方法があるかどうか疑問に思っています。
シャンティングヤードアルゴリズムに似た2つの状態の2つのスタックモデルを使用します。 2つの状態は、単項状態とバイナリ状態です。各状態で、トークンを処理(またはエラーを発行)してから、同じ状態を継続するか、他の状態に切り替えます。
単項状態で、開いた括弧が見つかった場合、(
、それは演算子のグループ化であり、あなたは単項状態のままです。 (単項状態では、識別子は変数参照であり、オペランドスタックにプッシュして、バイナリ状態に切り替えます。マイナス、-
、は単項否定であり、単項状態のままです。)
バイナリ状態では、開いた括弧が見つかれば、それは関数呼び出しです(そして、単項状態に切り替えます)。 2つの状態を区別することは、単項演算子と前置/後置演算子の処理にも役立ちます。 (ここではマイナス、-
はバイナリ減算であり、単項状態に切り替えます。)
oK、式(*p[i++])()
、p
は関数へのポインターへのポインターですよね?そう、
(空のパラメーターリスト式は、関数呼び出しが実際に存在する場合にのみ許可されることに注意してください。さまざまな可能性のあるエラーを検出するためのさまざまな方法と場所があります。)
単項状態とバイナリ状態で処理される実際のトークンは、解析しようとしている言語によって決まります。単項状態とバイナリ状態の別々のコードセクションを許可します。それらを混在させる意味はないようで、状態変数も必要ありません(コードセクションでは、どれがそれかを知っているだけです)。
これは、識別子の直後に続く開始ブラケットです。したがって、左角かっこに出会ったときに、最後に解析したトークンが識別子であるか演算子であるかを確認します。演算子である場合、それは単なる部分式です。そうでない場合、それは関数呼び出しの一部です。
トークンストリームを解析する前に、コメントを処理する必要があります。
これにより、コードの最後のビットがidentifier comment openBracket identifier closeBracket
に変わります。ここでASTを構築するとき、またはシャントヤードを行うときにcomment
トークンを無視できます。