私の理解では、パーサーは解析ツリーを作成し、その後それを破棄します。ただし、抽象構文ツリーをポップアウトすることもできます。これは、コンパイラが使用すると思われます。
私は、解析ツリーと抽象構文ツリーの両方が解析段階で作成されているという印象を受けています。次に、これらが異なる理由を誰かが説明できますか?
私の理解では、パーサーは解析ツリーを作成し、その後それを破棄します。ただし、抽象構文ツリーをポップアウトすることもできます。これは、コンパイラが使用すると思われます。
私は、解析ツリーと抽象構文ツリーの両方が解析段階で作成されているという印象を受けています。次に、これらが異なる理由を誰かが説明できますか?
回答:
解析ツリーは、具象構文ツリーとも呼ばれます。
基本的に、抽象ツリーの情報はコンクリートツリーの情報よりも少なくなります。具象ツリーには言語の各要素が含まれていますが、抽象ツリーには関心のない部分が捨てられています。
たとえば、次の式: (2 + 5) * 8
コンクリートはこんな感じ
( 2 + 5 ) * 8
| \ | / | | |
| \|/ | | |
\___|__/ | |
\______|/
一方、抽象ツリーには次のものがあります。
2 5
\/
+ 8
\/
*
具体的なケースでは、括弧と言語のすべての部分がツリーに組み込まれています。抽象の場合、括弧はなくなりました。これは、その情報がツリー構造に組み込まれているためです。
最初に理解する必要があるのは、特定の方法でパーサーやコンパイラを書くことを強制する人はいないということです。具体的には、パーサーの結果がツリーである必要は必ずしもありません。入力を表すのに適した任意のデータ構造を使用できます。
たとえば、次の言語:
prog:
definition
| definition ';' prog
;
definition: .....
定義のリストとして表すことができます。(Nitpickersはリストが縮退ツリーであることを指摘しますが、とにかく。)
次に、解析ツリー(またはパーサーが返したデータ構造)を保持する必要はありません。それどころか、コンパイラは通常、一連のパスとして構築され、前のパスの結果を変換します。したがって、コンパイラの全体的なレイアウトは次のようになります。
parser :: String -> Maybe [Definitions] -- parser
pass1 :: [Definitions] -> Maybe DesugaredProg -- desugarer
pass2 :: DesugaredProg -> Maybe TypedProg -- type checker
pass3 :: TypedProg -> Maybe AbstractTargetLang -- code generation
pass4 :: AbstractTargetLang -> Maybe String -- pretty printer
compiler :: String -> Maybe String -- transform source code to target code
compiler source = do
defs <- parser source
desug <- pass1 defs
typed <- pass2 desug
targt <- pass3 typed
pass4 targt
結論:構文解析ツリー、抽象構文ツリー、具体的な構文ツリーなどについて人々が話すのを聞いた場合、常に特定の目的に適したデータ構造に置き換えてください。