摘要:方法的產生式如下由得這個函數,包含了除布爾值的表達式之外的,各個表示數據得表達式的解析部分。這里我的鏈接直接指向了上關于線性漸變的形式語法部分,可以看到這部分對線性漸變語法的描述,和我上面解析的時候所用的產生式如出一轍。
博客源地址:https://github.com/LeuisKen/l...方法說明
相關評論還請到 issue 下。
san.parseExpr是San中主模塊下的一個方法。用于將源字符串解析成表達式對象。該方法和san.evalExpr是一對,后者接收一個表達式對象和一個san.Data對象作為參數,用于對表達式進行求值。如下例:
/** * 解析表達式 * * @param {string} source 源碼 * @return {Object} 表達式對象 */ function parseExpr(source) {} /** * 計算表達式的值 * * @param {Object} expr 表達式對象 * @param {Data} data 數據容器對象 * @param {Component=} owner 所屬組件環境,供 filter 使用 * @return {*} */ function evalExpr(expr, data, owner) {} san.evalExpr(san.parseExpr("1+1"), new san.Data()); // 2 san.evalExpr(san.parseExpr("1+num"), new san.Data({ num: 3 })); // 4
多帶帶拿出parseExpr來分析,其根據源字符串生成表達式對象,從San的表達式對象文檔中,可以看到San支持的表達式類型以及這些表達式對象的結構。我們在這里簡單記錄一下,parseExpr需要解析的表達式都有哪些:
TertiaryExpr:三元表達式
LogicalORExpr:邏輯或
LogicalANDExpr:邏輯與
EqualityExpr:判等
RelationalExpr:關系(大于、小于等)
AdditiveExpr:加減法
MultiplicativeExpr:乘除法、取余運算
UnaryExpr:一元表達式
ParenthesizedExpr:括號表達式
除了上述表示運算關系的表達式外,還有表示數據的表達式,如下:
String:字符串
Number:數組
Boolean:布爾值
ArrayLiteral:數組字面量
ObjectLiteral:對象字面量
Accessor:訪問器表達式
由于Accessor存在意義,是為了在evalExpr階段從Data對象中獲取數據,所以這里我將Accessor歸類為表示數據的表達式。
現在我們知道了所有的表達式類型,那么,parseExpr是如何從字符串中,解析出表達式對象的呢?
如何讀取字符串parseExpr方法定義在src/parser/parse-expr.js中。我們可以看到其依賴了一個Walker類,注釋中的說明是字符串源碼讀取類。
Walker類包含以下內容:
屬性:this.source:保存要讀取的源字符串
this.len:保存源字符串長度
this.index:保存當前對象讀取字符的位置
方法:currentCode方法:返回當前讀取字符的 charCode
charCode方法:返回指定位置字符的 charCode
cut方法:根據指定起始和結束位置返回字符串片段
go方法:將this.index增加給定數值
nextCode方法:讀取下一個字符并返回它的 charCode
goUntil 方法/** * 向前讀取字符,直到遇到指定字符再停止 * 未指定字符時,當遇到第一個非空格、制表符的字符停止 * * @param {number=} charCode 指定字符的code * @return {boolean} 當指定字符時,返回是否碰到指定的字符 */ Walker.prototype.goUntil = function (charCode) { var code; while (this.index < this.len && (code = this.currentCode())) { switch (code) { // 空格 space case 32: // 制表符 tab case 9: this.index++; break; default: if (code === charCode) { // 找到了 this.index++; return 1; } // 沒找到 return; } } };match 方法
/** * 向前讀取符合規則的字符片段,并返回規則匹配結果 * * @param {RegExp} reg 字符片段的正則表達式 * @param {boolean} isMatchStart 是否必須匹配當前位置 * @return {Array?} */ Walker.prototype.match = function (reg, isMatchStart) { reg.lastIndex = this.index; var match = reg.exec(this.source); /** * 這里是源碼的實現,簡潔但是有點晦澀,后面我把邏輯運算符拆成了 if else,希望能好理解一些 if (match && (!isMatchStart || this.index === match.index)) { this.index = reg.lastIndex; return match; } */ if (match) { // 如果是必須匹配當前位置 // 這個標記是 3.5.11 的時候加上的,changelog 表述為: // 【優化】- 在 dev 模式下,增加一些表達式解析錯誤的提示 if (isMatchStart) { // 判斷當前讀取字符的 index,是否和匹配結果第一個字符的 index 相等 if (this.index === match.index) { this.index = reg.lastIndex; return match; } } // 不必須匹配當前位置 else { this.index = reg.lastIndex; return match; } } };如何處理運算符的優先級
在初看parseExpr實現的時候,這就是一個困擾我的難題。學習過程中,我看到San最先是將表達式丟給一個讀取三元表達式的方法,這個方法里面去調用讀取邏輯或表達式的方法,邏輯或里面調用邏輯與,邏輯與里面調用判等,判等里面調用關系??看得我可以說是云里霧里。雖然大致能明白這是在處理運算優先級,但是我覺得肯定有一個更上層的指導思想來讓San選擇這一方案。
為了尋找這個“指導思想”,我轉頭去看了一段時間的編譯原理,大致上理清了這部分思路。考慮到有些同學應該也和我一樣沒有系統地學習過這門課程,因此我在下面取《編譯原理》中的例子來予以說明(下文內容包含了很多定義性的內容,且為了保證嚴謹,很多定義都是直接照搬書上的,所以如果你對這部分足夠熟悉,跳過即可。)
上下文無關文法及其構成假設我們現在要解析的expr是一個十以內的四則運算算式(編譯原理將其視為一種語言),其包括加減乘除( +、-、*、/ )四則運算。我們可以使用一種叫做產生式的方式,來表示表達式的解析規則。有了產生式,我們可以將一個算式的解析規則表達成如下形式(這一解析過程被稱為詞法分析):
expr ---> digit // 這里的 digit 指 0,1,2,3...9 這十個數字 | expr + expr // 豎線(|)表示或,這一行定義了加法 | expr - expr // 減法 | expr * expr // 乘法 | expr / expr // 除法 | (expr) // 加括號
這里介紹幾個概念,這里的digit和+ - * / ()等符號,被稱為終結符號,表示語言中不可再分的基本符號;而像expr這樣能夠用于表示終結符號序列的變量,被稱為非終結符號。
我們都知道,十以內的四則運算算式的解析是與上下文無關的。在編譯原理中,將描述語言構造的層次化語法結構稱為“文法”(grammar),我們的十以內的四則運算算式就是一個“上下文無關文法”(context-free grammar)。編譯原理中定義了上下文無關文法由四個元素構成:
終結符號集合
非終結符號集合
產生式集合
一個指定的非終結符號作為開始符號(上面的expr)
語法分析樹語法分析樹是一種圖形表示,他展現了從文法的開始符號推導出相應語言中的終結符號串的過程。例如一個給定一個算式:9 - 5 + 2,可以表示成如下的語法分析樹:
expr expr + expr expr - expr digit digit digit 2 9 5二義性及其消除
單純從 9 - 5 + 2 出發去畫語法分析樹,還能得到另一種結果,如下:
expr expr - expr digit expr + expr 9 digit digit 5 2
如果我們從下往上對語法分析樹進行計算,前一棵樹先計算 9 - 5 得 4,然后 4 + 2 得 6,但后一棵樹的結果則是 5 + 2 得 7,9 - 7 得 2。這就是文法得二義性,其定義為:對于同一個給定的終結符號串,有兩棵及以上的語法分析樹。由于多棵樹意味著多個含義,我們需要設計沒有二義性的文法,或給二義性文法添加附加規則來對齊進行消除。
在本例中,我們采用設計文法的方式來消除二義性。由于四則運算中,加減位于一個優先級層次,乘除位于另一個,我們創建兩個非終結符號expr和term分別對應這兩個層次,并使用另一個非終結符號factor來生成表達式中的基本單元,可得到如下的產生式:
factor ---> digit | (expr) // 考慮乘法和加法的左結合性 term ---> term * factor | term / factor | factor expr ---> expr + term | expr - term | term
有了新的文法之后,我們再看 9 - 5 + 2,其僅能生成如下的唯一語法分析樹:
expr expr + term expr - term factor term factor digit factor digit 2 digit 5 9parseExpr 的實現
現在我們回到San中的表達式,有了前面的基礎,相信大家都已經清楚了parseExpr解析表達式源字符串方法的緣由。接下來,我們只要合理的定義出來“San中的表達式”這一語言的產生式,函數實現就水到渠成了。
表達式解析入口parseExpr:
/** * 解析表達式 * * @param {string} source 源碼 * @return {Object} */ function parseExpr(source) { if (typeof source === "object" && source.type) { return source; } var expr = readTertiaryExpr(new Walker(source)); expr.raw = source; return expr; }
其對應的產生式就是:
Expr ---> TertiaryExpr
readTertiaryExpr:
/** * 讀取三元表達式 * * @param {Walker} walker 源碼讀取對象 * @return {Object} */ function readTertiaryExpr(walker) { var conditional = readLogicalORExpr(walker); walker.goUntil(); if (walker.currentCode() === 63) { // ? walker.go(1); var yesExpr = readTertiaryExpr(walker); walker.goUntil(); if (walker.currentCode() === 58) { // : walker.go(1); return { type: ExprType.TERTIARY, segs: [ conditional, yesExpr, readTertiaryExpr(walker) ] }; } } return conditional; }
可以看到,判斷條件部分conditional是readLogicalORExpr的結果。如果存在?、:兩個和三元表達式相關的終結符號,就返回一個三元表達式類型的表達式對象;否則直接返回conditional。可知產生式:
TertiaryExpr ---> LogicalORExpr ? TertiaryExpr : TertiaryExpr | LogicalORExpr
由readLogicalORExpr可得產生式:
LogicalORExpr ---> LogicalORExpr || LogicalANDExpr | LogicalANDExpr
由readLogicalANDExpr得:
LogicalANDExpr ---> LogicalANDExpr && EqualityExpr | EqualityExpr
由readEqualityExpr得:
EqualityExpr ---> RelationalExpr == RelationalExpr | RelationalExpr != RelationalExpr | RelationalExpr === RelationalExpr | RelationalExpr !== RelationalExpr | RelationalExpr
由readRelationalExpr得:
RelationalExpr ---> AdditiveExpr > AdditiveExpr | AdditiveExpr < AdditiveExpr | AdditiveExpr >= AdditiveExpr | AdditiveExpr <= AdditiveExpr | AdditiveExpr
readAdditiveExpr:
/** * 讀取加法表達式 * * @param {Walker} walker 源碼讀取對象 * @return {Object} */ function readAdditiveExpr(walker) { var expr = readMultiplicativeExpr(walker); while (1) { walker.goUntil(); var code = walker.currentCode(); switch (code) { case 43: // + case 45: // - walker.go(1); // 這里創建了一個新對象,包住了原來的 expr,返回了一個新的 expr expr = { type: ExprType.BINARY, operator: code, segs: [expr, readMultiplicativeExpr(walker)] }; // 注意到這里是 continue,之前的函數都是 return continue; } break; } return expr; }
讀加法的這個函數有些特殊,其在第一步先調用了讀乘法的方法,得到了變量expr,然后不斷地更新expr對象包住原來的對象,以保證結合性的正確。
方法的產生式如下:
AdditiveExpr ---> AdditiveExpr + MultiplicativeExpr | AdditiveExpr - MultiplicativeExpr | MultiplicativeExpr
由readMultiplicativeExpr得:
MultiplicativeExpr ---> MultiplicativeExpr * UnaryExpr | MultiplicativeExpr / UnaryExpr | MultiplicativeExpr % UnaryExpr | UnaryExpr
readUnaryExpr這個函數,包含了除布爾值的表達式之外的,各個表示數據得表達式的解析部分。因此對應的產生式也相對復雜,為了便于說明,我自行引入了一些非終結符號:
UnaryExpr ---> !UnaryExpr | "String" | "String" | Number | ArrayLiteral | ObjectLiteral | ParenthesizedExpr | Accessor ArrayLiteral ---> [] | [ElementList] // 這里引入一個新的非終結符號 ElementList 來輔助說明 ElementList ---> Element | ElementList, Element Element ---> TertiaryExpr | ...TertiaryExpr ObjectLiteral ---> {} | {FieldList} // 類似上面的 ElementList FieldList ---> Field | FieldList, Field Field ---> ...TertiaryExpr | SimpleExpr | SimpleExpr: TertiaryExpr SimpleExpr ---> true | false | "String" | "String" | Number
由readParenthesizedExpr得:
ParenthesizedExpr ---> (TertiaryExpr)
由readAccessor得:
Accessor ---> true | false | Identifier MemberOperator* // 此處 * 表示 0個或多個的意思 MemberOperator ---> .Identifier | [TertiaryExpr]
至此,我們終于把所有的產生式都梳理清楚了。
和 JavaScript 文法的對比在這里我附上一份JavaScript 1.4 Grammar供參考。通過對比兩種文法產生式的不同,能找到很多兩者之間解析結果得差異。下面是一個例子:
1 > 2 < 3 // 返回 true,相當于 1 > 2 返回 false,false < 3 返回 true san.evalExpr(san.parseExpr("1 > 2 < 3"), new san.Data()); // 返回 false
注意到 San 中關于RelationalExpression的產生式是:
RelationalExpr ---> AdditiveExpr > AdditiveExpr | AdditiveExpr < AdditiveExpr | AdditiveExpr >= AdditiveExpr | AdditiveExpr <= AdditiveExpr | AdditiveExpr
也就是說,對于1 > 2 < 3,其匹配了RelationalExpr ---> AdditiveExpr > AdditiveExpr。其中1傳入了AdditiveExpr解析成Number的1;2 < 3則被視為另一個AdditiveExpr進行解析,由于后面已經沒有能夠處理<的邏輯了,所以會被解析成Number的2。所以,輸入的1 > 2 < 3,真正解析出來的就只有1 > 2了,所以上面的代碼會返回 false 。
個人認為 San 在這里應該是刻意為之的。因為對于1 > 2 < 3這種表達式,真的沒必要保證它按照JavaScript的文法來解析——這種代碼寫出來肯定是要改的,沒有顧及它的意義。
拓展了解了 parseExpr 是如何從源字符串得到表達式對象之后,也就發現其實很多地方都用了類似的方法來描述語法。比如CSS 線性漸變。這里我的鏈接直接指向了MDN上關于線性漸變的形式語法(Formal syntax)部分,可以看到這部分對線性漸變語法的描述,和我上面解析 parseExpr 的時候所用的產生式如出一轍。
linear-gradient( [| to ,]? [, ]+ ) ---------------------------------/ ----------------------------/ Definition of the gradient line List of color stops where = [left | right] || [top | bottom] and = [ | ]?
這種語法形式是MDN定義的CSS屬性值定義語法。
參照我們前面所寫的產生式與上面的CSS屬性值定義語法,我寫出了如下的產生式:
expr ---> gradientLine , colorStopList | colorStopList gradientLine ---> angle | to sideOrCorner sideOrCorner ---> horizon | vertical | horizon vertical | vertical horizon horizon ---> left | right vertical ---> top | bottom colorStopList ---> colorStopList, color distance | color distance color ---> hexColor | rgbColor | rgbaColor | literalColor | hslColor // 相信大家都懂,我就不做進一步展開了 distance ---> percentage | length // 同上,不做進一步展開結語
這一趟下來可以說是補了不少課,也揭示了 San 中內部原理的一角,后面計劃把 evalExpr、Data、parseTemplate等方法也學習一遍,進一步了解 San 的全貌。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95530.html
摘要:此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考整理與心得體會,此文會不斷更新視頻傳送門每日學習記錄使用錄像設備記錄每天的學習源碼學習源碼學習內存管理筆記源碼學習內存管理筆記源碼學習內存管理筆記源碼學習基本變量筆記 此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考、整理與心得體會,此文會不斷更新 視頻傳送門:【每日學習記錄】使用錄像設備記錄每天的學習 PHP7...
摘要:有如下模塊源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析使用和監控和博客從到學習介紹從到學習上搭建環境并構建運行簡單程序入門從到學習配置文件詳解從到學習介紹從到學習如何自 Flink Metrics 有如下模塊: Flink Metrics 源碼解析 —— Flink-metrics-core Flink Metrics 源碼解析 —— Flink-metr...
摘要:模塊中的類結構如下博客從到學習介紹從到學習上搭建環境并構建運行簡單程序入門從到學習配置文件詳解從到學習介紹從到學習如何自定義從到學習介紹從到學習如何自定義從到學習轉換從到學習介紹中的從到學習中的幾種詳解從到學習讀取數據寫入到從到學 Flink-Client 模塊中的類結構如下: https://t.zsxq.com/IMzNZjY showImg(https://segmentfau...
閱讀 3189·2023-04-26 03:06
閱讀 3689·2021-11-22 09:34
閱讀 1134·2021-10-08 10:05
閱讀 3024·2021-09-22 15:53
閱讀 3530·2021-09-14 18:05
閱讀 1387·2021-08-05 09:56
閱讀 1879·2019-08-30 15:56
閱讀 2124·2019-08-29 11:02