開始寫第四篇,別的不說了。這篇將涉及到如何使用字符串解析結果,生成一個 Lambda 表達式樹。東西有點多,我先整理一下思路,在下面說明一下。如果你有問題,在后面的評論上寫下來,我看到了會回復你。
在前幾篇中,我寫了一個字符串解析模塊,還有一個根據字符串解析成 Type 對象的類型分析模塊。這兩個模塊,都將為這個 Expression 核心解析類提供輔助,所以如果前兩篇文章你沒有看到,可以到我博客中先把前幾篇文章看一下,理解我的整體思路后,再回過頭來看這篇。
我希望從這篇文章開始,看到一些有用的評論,謝謝了!
Expression 核心解析類(ExpressionParserCore)對外發布的只有一個方法 —— ToLambdaExpression ,用於輸出分析得到的 Lambda 表達式樹。在貼代碼之前,我把大致思路說明一下:
首先,需要幾個前提條件:
1. 待分析的字符串
2. Lambda 表達式樹包含的委托類型
3. 類型解析可能用到的命名空間和程序集列表
第一個,很好理解,在這里不做闡述。
第二個,是告訴要解析的目標參數和返回值信息。
第三個,類型解析中會用到,這里只是做一下參數傳遞。具體的代碼可以參看第三篇。
其次,由於 Lambda 表達式樹的遞歸特性,所以其解析工作不可能一蹴而就,所以解析工作將其拆分為三步:
1. ReadFirstExpression
2. ReadNextExpression
3. ReadExpression
從字面上可以看出來:ReadFirstExpression 是讀取第一個 Expression ,ReadNextExpression 是讀取接下來的 Expression ,ReadExpression 是讀取 Expression 。
ReadFirstExpression:讀取第一個 Expression ,不去管后面是不是還有沒有解析的 Expression 。比如:【() => 1 + 2】這個表達式,如果使用該方法則只會讀取常量表達式【1】,不會繼續讀取剩下的常量表達式【2】和加法表達式。一句話總結:讀取了一個 Expression 就停止讀取。
ReadNextExpression:讀取下一個 Expression ,在已讀取了表達式的基礎上繼續解析 Expression 。接着上面的表達式【() => 1 + 2】繼續說,讀取了表達式【1】后,繼續讀取常量表達式【2】,再繼續讀取加法表達式,返回整個表達式。
到了這里,有人會問:為什么會分成讀取第一個和讀取下一個兩個方法?有以下原因吧,我感覺分開好些:
1. 有些表達式不能作為第一個表達式(比如加法表達式),有些不能作為后續的表達式(比如求反表達式)。
2. 就是有些符號不能在開始表達式中(比如右括號),有些不能作為在結束表達式中(比如左括號)。
3. 開始表達式從零構造一個表達式,后續表達式需要一個前置表達式。
還有一些,暫時想到的就這些了。
ReadExpression:讀取表達式,可以遞歸調用實現 Expression 的遞歸。在發布的方法 ToLambdaExpression 中,調用的就是 ReadExpression ,進行第一次的遞歸操作。在表達式中如果需要,可能會多次調用,比如表達式【() => 1 + 2】會調用兩次:一次是開始調用 ToLambdaExpression 的時候,一次是解析常量表達式【2】的時候。
在此,我把大概的流程用【() => 1 + 2】說明一下吧:
1. 准備前提條件。
2. 將字符串【() => 1 + 2】解析為我們需要的 Token 單元集合。
3. 分析目標委托類型(可以理解為 typeof(Func<int>).GetType() )為 0 個參數,返回值為 int 的委托。
4. 調用 ToLambdaExpression 開始解析 Lambda 表達式樹:第一次調用 ReadExpression 方法。
5. ReadExpression 方法調用 ReadFirstExpression 方法獲取一個常量表達式樹【1】。
6. ReadExpression 方法繼續調用 ReadNextExpression 方法,並傳遞參數常量表達式樹【1】。
7. 讀取到加法表達式樹,將常量表達式樹【1】作為加法左邊的表達式樹,尚需要的右邊表達式樹,調用 ReadExpression 方法讀取。
8. ReadExpression 方法調用 ReadFirstExpression 方法獲取一個常量表達式樹【2】。
9. ReadExpression 方法繼續調用 ReadNextExpression 方法,並傳遞參數常量表達式樹【2】。
10. 發現已經讀取到末尾,ReadNextExpression 方法返回傳遞進來的參數常量表達式樹【2】。
11. ReadExpression 返回讀取到的常量表達式【2】,這里將返回到第 7 步。
12. 加法表達式的左右表達式均已具備,構造加法表達式【1 + 2】,並返回到第 6 步。
13. 繼續讀取發現讀取到末尾,返回加法表達式【1 + 2】到第 4 步。
14. 根據委托類型構造 Lambda 表達式樹並返回,解析完成。
我先將這三個方法的原型說明一下:
1. ReadFirstExpression()
2. ReadNextExpression(int level, ReadResult previousResult, TokenId? wrapStart = null)
3. ReadExpression(int level = 0)
level : 這個參數是運算符優先級,優先級越大該數字越大,比如【1 + 2 * 3】這個表達式,會先進行【2 * 3】的組合而不是【1 + 2】的組合。
previousResult: 表示前面分析得到的結果。包含兩個屬性:表達式和是否讀取結束。
wrapStart : 表示讀取下一個表達式的前導 Token 類型。previousResult 中的屬性【是否讀取結束】和這個參數相關:每種括號的開始和結束對應。
就到這里吧,寫太長了,估計多數人沒看下去的興趣。下篇我會把 ReadFirstExpression 方法的邏輯整理一下,順便貼出來部分代碼。如果你感覺我寫的東西有用,或者你期待下一篇,請點一下推薦,謝謝了!