言語解析
字句解析
字句解析は、プログラムやスクリプトを「意味のある最小単位の文字列」に分解することです。
この最小単位をトークンといいます。
たとえば、以下のNScripterのスクリプトを読み込むとします。
; 定義ラベル *define game ; 実行開始ラベル *start こんにちは@ wave "se.wav" さようなら\ end
これを字句解析するとこんな感じです。
* define game * start こんにちは @ wave se.wav さようなら \ end
スクリプトの実行に影響のないコメントは、この段階で削除しています。
単純に、
- スペース
- タブ
- 改行
で分解していけばOKです。
ただ、改行が特別な意味を持つ場合は、トークンとして残しておく必要があります。
NScripterは、行頭に*があると後ろの文字列をラベルとして扱うので、
特殊な文字列に置き換えたり、行番号・列番号を持たせておくのもありかもしれません。
あと、たいていの言語は””で囲まれていると、
文字列として扱うので、この段階で切り出してしまいます。
構文解析
構文解析では、字句解析でバラバラに分解したトークンを、
扱いやすいようにまとめることです。
まとめ方としては、
- 文
- 式
にまとめます。
例えば、NScripterの、
if %0=%1 gosub *eq
というスクリプトは、
gosub *eq …(1)
が「文」になります。(正確にはgosub文)
また、
%0=%1 …(2)
のように演算を行うまとまりを「式」と呼びます。
そして、このスクリプト全体が「if文」となります。
つまり、
if文に(1)文と(2)式がぶら下がっている
ような構造になります。
プログラム的には、
#include <string> #include <vector> /** * 構文木ノードクラス */ class CTree { private: string m_value; // 値 vector<CTree*> m_childNodes; // 子ノード public: // コンストラクタ CTree(string value) { SetValue(value); } // 値の設定 void SetValue(string value) { m_value = value; } // 子ノードの追加 void AppendChild(CTree *tree) { m_childNodes.push_back(tree); } };
というクラスを定義しておきます。
構文木クラスを使って先ほどのスクリプトを構文解析すると、
こんな感じになります。
// if文 CTree *treeIf = new CTree("IfStatement"); // 式 CTree *treeExp = new CTree("Expression"); CTree *treeParam0 = new CTree("%0"); treeExp.AppendChild(treeParam0); CTree *treeEq = new CTree("="); treeExp.AppendChild(treeEq); CTree *treeParam1 = new CTree("%1"); treeExp.AppendChild(treeParam1); treeIf.AppendChild(treeExp); // 文 CTree *treeGosub = new CTree("GosubStatement"); CTree *treeLavel = new Ctree("eq"); treeGosub.AppendChild(treeLavel); treeIf.AppendChild(treeGosub);
(つづく)