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