語法分析器初步學習——LISP語法分析

語法分析器初步學習——LISP語法分析ios

         本文參考自vczh的《如何手寫語法分析器》。數據結構

         LISP的表達式是按照前綴的形式寫的,好比(1+2)*(3+4)在LISP中會寫成(*(+ 1 2)(+ 3 4)),1 + 2會寫成(+ 1 2)。函數

         LISP語言的語法以下形式:學習

1.Operator = 「+」 | 「-」 | 「*」 | 「/」測試

         2.Expression = <數字> | 」(」Expression」)」 | 「(」Operator Expression Expression」)」spa

         咱們根據以上兩條語法規則來寫代碼:設計

// LISP語法分析器
#include <iostream>
#include <string>
using namespace std;

// 檢測是不是空白符
bool IsBlank(char ch)
{
    return ch == ' ' || ch == '\t';
}

// 檢測Text是不是Stream的前綴
// 若是是前綴,則返回true,並將pos前移Text.size()個字符
// 若是不是前綴,則返回false
// 此函數一開始會過濾掉Stream開頭的空格
bool IsPrefix(const string& Stream, int& pos, const string& Text)
{
    int read = pos;

    // 過濾空白符
    while (IsBlank(Stream[read]))
    {
        ++read;
    }
    // 不能寫爲:
    // while (IsBlank(Stream[read++]));
    // 由於這樣寫會致使read至少加1

    if (Stream.substr(read, Text.size()) == Text) // 若是是前綴
    {
        pos = read + Text.size();
        return true;
    }
    else
    {
        return false;
    }
}

// 檢測Stream開頭是不是操做符+、-、*、/
// 是的話,函數返回實際的操做符,並將pos便宜到操做符以後
// 不然返回0
// 判斷語法1:Operator = 「+」 | 「-」 | 「*」 | 「/」
char IsOperator(const string& Stream, int& pos)
{
    if (IsPrefix(Stream, pos, "+")
     || IsPrefix(Stream, pos, "-")
     || IsPrefix(Stream, pos, "*")
     || IsPrefix(Stream, pos, "/")) // 若是開頭是操做符
    {
        return Stream[pos - 1]; // 若是是的話,pos已經向前偏移了
    }
    else
    {
        return 0;
    }
}

// 表達式結構體
struct Expression
{
    int Result;   // 返回表達式結果
    string Error; // 返回錯誤信息,沒錯誤則爲空
    int Start;    // 錯誤發生的位置

    Expression() : Result(0), Start(0) {}
};

// 檢測Stream開頭是不是數字,若是是,則將pos便宜到數字以後
// 函數返回Expression
// 判斷語法2中的第一部分:Expression = <數字>
Expression GetNumber(const string& Stream, int& pos)
{
    Expression Result;

    bool GotNumber = false;

    int read = pos;

    // 過濾空白符
    while (IsBlank(Stream[read]))
    {
        ++read;
    }

    while (true)
    {
        // 依次讀入一個字符
        char ch = Stream[read];
        if (ch >= '0' && ch <= '9')
        {
            Result.Result = Result.Result * 10 + ch - '0';
            GotNumber = true;
            ++read;
        }
        else
        {
            break;
        }
    }

    if (GotNumber)
    {
        pos = read;
    }
    else
    {
        Result.Error = "這裏須要數字";
        Result.Start = read;
    }

    return Result;
}

// 檢測Stream開頭是不是表達式
// 若是是,則將pos前移到表達式後
// 實現語法2:Expression = <數字> | 「(」Expression「)」 | 「(」Operator Expression Expression「)」
Expression GetExpression(const string& Stream, int& pos)
{
    int read = pos;

    // 檢測開頭是不是數字
    // 語法2第一部分:Expression = <數字>
    Expression Result = GetNumber(Stream, read);

    if (!Result.Error.empty()) // 若是開頭不是數字
    {
        if (IsPrefix(Stream, read, "(")) // 檢測是否"("開頭
        {
            // 將Result的Error清空
            Result.Error.clear();

            char Operator = 0;
            if ((Operator = IsOperator(Stream, read)) != 0)
            // 若是是操做符,語法2第三部分:Expression = 「(」Operator Expression Expression「)」
            {
                // 獲取左參數
                // 遞歸調用
                Expression left = GetExpression(Stream, read);
                if (!left.Error.empty())
                {
                    return left;
                }
                // 保持當前read
                int rightRead = read;
                // 獲取右參數
                // 遞歸調用
                Expression right = GetExpression(Stream, read);
                if (!right.Error.empty())
                {
                    return right;
                }

                // 根據操做符Operator進行計算
                switch (Operator)
                {
                case '+':
                    Result.Result = left.Result + right.Result;
                    break;

                case '-':
                    Result.Result = left.Result - right.Result;
                    break;

                case '*':
                    Result.Result = left.Result * right.Result;
                    break;

                case '/':
                    if (right.Result == 0) // 除數爲0
                    {
                        Result.Error = "除數爲0";
                        Result.Start = rightRead;
                    }
                    else
                    {
                        Result.Result = left.Result / right.Result;
                    }
                    break;

                default: // 這種狀況不會發生,由於前提是Operator,因此只有+、-、*、/四種狀況
                    Result.Error = "未知的操做符";
                    Result.Start = read;
                    return Result;
                }
            }
            else
            // 若是不是操做符
            // 語法2的第二部分:Expression = 「(」Expression「)」
            {
                // 獲取表達式
                Result = GetExpression(Stream, read);
                // 若是獲取失敗,則直接返回
                if (!Result.Error.empty())
                {
                    return Result;
                }
            }

            // 檢測是否有配套的")"
            if (!IsPrefix(Stream, read, ")"))
            {
                Result.Error = "此處缺乏右括號";
                Result.Start = read;
            }
        }
    }

    // 若是沒有出錯,則更新pos
    if (Result.Error.empty())
    {
        pos = read;
    }

    // 檢測是否有配套")"時,若是不存在,能夠直接將Result返回
    // 這樣在後面就不用檢測是否出錯了,由於前面凡是出錯的狀況
    // 都返回了,這樣就不用檢測了,而直接更新pos: pos = read

    return Result;
}


// 測試
int main()
{
    while (true)
    {
        string Stream;
        cout << "輸入一個LISP表達式" << endl;
        getline(cin, Stream);

        int pos = 0;

        if (IsPrefix(Stream, pos, "exit"))
        {
            break;
        }

        pos = 0;

        Expression Result = GetExpression(Stream, pos);
        if (!Result.Error.empty())
        {
            cout << "表達式錯誤" << endl;
            cout << "位置:" << Result.Start << endl;
            cout << "錯誤信息:" << Result.Error << endl;
        }
        else
        {
            cout << "結果:" << Result.Result << endl;
        }
    }

    return 0;
}

         下面對程序代碼解釋以下:3d

         數據結構code

         程序中用string型的字符串Stream來存儲用戶輸入的表達式,int型的pos 做爲當前掃描的位置。blog

         Expression結構體用來標識語法二中的Expression,Expression結構體能夠標識<數字> | 」(」Expression」)」 | 「(」Operator Expression Expression」)」三種形式的任意一種。Result元素用來記錄正確表達式時的計算結果,Error用來當語法分析表達式時遇到的錯誤時,將錯誤信息記錄下來。Start用來記錄錯誤的位置,準確來說是用來記錄最後正確的下一個位置,由於錯誤字符串前的空白符會被忽略。

         函數

         IsBlank:用來檢測char字符是否爲空白符。

         IsPrefix:用來檢測Text字符串是不是Stream從pos起頭的前綴。

         IsOperator:用來檢測Stream從pos起頭,是不是以操做符+、-、*、/開頭的。

         GetNumber:用來檢測Stream從pos起頭,是不是以數字開頭的。

         GetExpression:用來檢測Stream從pos開頭,是否以表達式開頭。

         語法分析器的關鍵在於寫出正確的語法,而後根據語法導出代碼。語法定義中存在遞歸定義,代碼中相應地也會出現遞歸調用。代碼應該一一對應的對應到語法定義。

         設計的巧妙之處

         pos和read:pos用來標識掃描的位置,pos只用來記錄正確分析的位置。在IsPrefix、GetNumber、GetExpression等函數中,都會在定義一個read,初始化爲pos,用來做爲實際的掃描遊標,只有在正確分析後,read纔會用來更新pos,不然read會返回給Result的Start,用來記錄錯誤發生的位置,而且直接返回Result。用read來代替pos的內部操做,能夠防止當分析失敗時,還要講pos還原的問題。

         Expression結構體:Expression結構包含三個元素,Start和Error字段能夠很地記錄錯誤發生的位置以及錯誤信息。這樣能夠很好的處理髮生錯誤的狀況。

 

         以上是最爲簡單的遞歸降低語法分析,更多的語法分析方面的知識有待進一步學習。

相關文章
相關標籤/搜索