• 沒有找到結果。

5-1 smcSL 編譯器語法層面處理流程

首先 smcSL 編譯器一如一般編譯器原理,也有包含如下圖所示那樣的語法 剖析,到產生目的碼的過程。一個比較特別的地方在於,smcSL 基於安全性的 原因會將一份原始碼編譯後產生兩份目的碼,此特點將有別於其他一般語言。

圖 5-1:smcSL 編譯器編譯流程示意圖

就實作細節而言,如果從語法處理的前階段來看,本研究與之前版本的編 譯器差別在於,新增了基於標準 Lex&Yacc 所開發出來的 C/C++語法掃描與剖 析器。此前的版本實際上是使用本編譯器的開發語言,即 Ruby 本身內建的 樹兩者功能做切割。具體而言,現在的實作上分為 Lexer& Parser、Builder 與 AST Template 三部組件。其中 AST Template 指的是要如何從剖析出來的 節點輸出成 AST 所使用的 S-expression 格式,其將透過 Builder 填入必要資訊 的樣板檔。而 Lexer& Parser 的角色簡化到只有在遇見特定節點時,一一呼叫 相關 Builder,由後者執行填入樣板變數的動作。其流程示意圖如下所示

31

圖 5-3:smcSL 語法掃描與剖析階段內部流程圖

至於在設計上為何要將 Builder 與 AST Template 從 Lexer& Parser 中抽出,

這是考慮到將來語言增加新特性,例如在可預見的未來增函式樣板(Template) 的功能時,可以比較清楚且快速的增加與更改所對應的 S-expression 格式。

相較於以往的 smcSL 編譯器是將產生 S-expression 的功能寫在剖析器的程式 碼中,隨時間修改後沒有一份即時且統一的文件描述每個抽象語法樹節點是如 何對應到 S-expression;今此功能以 AST Template 方式提供後就可以很自然 的 將 增修 程式 與 更新文 件 兩者 行為 統 一,日 後 要檢 視全 部 節點對 應 到 的 S-expression,只要檢視 AST Template 即可。

另外前述所謂 S-expression 格式,是指為了要使透過剖析,已經是結構化 的抽象語法樹,可以順利輸出並為編譯器所接收而使用的序列化(Serialized)

格式。其源頭是來自 Lisp 語言表達程式結構的類似概念,並輕微變形成 Ruby 可以接收並輕易轉為 Ruby 程式結構的格式。S-expression 範例如下

圖 5-4:smcSL 語法掃描與剖析器產生 S-expression 格式範例

[:program, [:modules, [:list, [:module, [:name, :main], [:list], [:list, [:fn, [:name, :main], [:list], [:stmts, [:stmts, [:stmts, [:stmts, [:stmts, [:stmts, [:noop], [:write, :SMC_PRIVATE_A, [:str, "How much money do you have?\n"]]], [:write, :SMC_PRIVATE_B, [:str, "How much money do you have?\n"]]], [:read, [:idrefer, [:name, :asset_a], [:list]]]], [:read, [:idrefer, [:name, :asset_b], [:list]]]], [:asn, [:id, [:name, :empty], [:accessor]], [:call, [:id, [:name, :empty_fn], [:accessor, [:id, [:name, :main], [:accessor]]]], [:list, [:id, [:name, :empty], [:accessor]]]]]], [:return, [:call, [:id, [:name, :compare], [:accessor, [:id, [:name, :main], [:accessor]]]], [:list, [:id, [:name, :asset_a], [:accessor]], [:id, [:name, :asset_b], [:accessor]]]]]], [:list, [:return, [:call, [:id, [:name, :compare], [:accessor, [:id, [:name, :main], [:accessor]]]], [:list, [:id, [:name, :asset_a], [:accessor]], [:id, [:name, :asset_b], [:accessor]]]]]], [:sig, [:id, [:name, :main], [:accessor]], [:list,

[:sigdecl, :SMC_SHARED, :SMC_INT__0]]], [:list, [:vardecl, [:id, [:name, :asset_a], [:accessor]], :SMC_PRIVATE_A, :SMC_LONG__0, [:list]], [:vardecl, [:id, [:name, :asset_b], [:accessor]], :SMC_PRIVATE_B, :SMC_LONG__0, [:list]], [:vardecl, [:id, [:name, :empty], [:accessor]], :SMC_PRIVATE_B, :SMC_INT__0,

smcSL Lexer& Parser

Function =[‘fn’,<%= name %>, ..]

Expression=[‘expr’, <%= op %>, ..]

...

抽象語法樹節點

上例可以看出,整個 smcSL 程式經語法掃描與剖析後,產生的語法樹 以:program 為根,內中開始是:modules 到函式內部陳述式與運算式的元素,再 細至每個變數、每個常數。

至於 S-expression 的格式,在 smcSL 中並沒有程式性的規範,但有幾個 原則在設計與使用上必頇遵孚

一、 該節點的「型別」必頇位於第一個元素,例如 :program、:module 二、 帶有多個元素的串列,必頇用另一個 S-expression 的陣列包裹這些

元素。且該 S-expression 必頇以 :list 為其型別標示

三、 編譯器其他組件不應該直接操作陣列化的 S-expression,而應該透過 適當的轉換器 ,將每種 S-expression 轉成適切的抽象語法樹節點後,

方透過符合 Ruby 物件導向原則的方式對之進行操作

四、 前 述 的 轉 換 器 應 該 要 有 能 力 將 相 應 的 節 點 傾 印 (Dump) 回 S-expression,並保持所有資訊不在這過程中漏失

五、 S-expression 從語法掃描與剖析器產生後,應該儘量保持唯讀狀態。

也就是後續的操作除非有必要,否則不應該改變整個程式原始的抽象 語法樹

最後,從編譯流程上來看,由於 S-expression 在 Ruby 中就是普通的陣列 而已。因此在 smcSL 編譯器透過系統呼叫語法掃描與剖析器後,所接收到的系 統回傳就是這樣的字串,而後只要使用 eval 功能就可以取得前者所剖析出來的 抽象語法樹。而此處的抽象語法樹即是後續所有從分析到產生目的碼之基礎。

smcSL 編譯器於語法層面的處理,還有一個值得注意的特色是本研究中,

因 smcSL 語言之常數區段,實際上會有常數計算的需求。因此目前編譯器實際 上具備一個前處理器(Preprocessor)去處理這些常數區段中的計算,如圖所示

smcSL 原始檔常數區段範例 經前處理器處理之結果

1 Const pi = 3.14 1

2 Const tau = pi * 2 2

3 Const hello = “Hello, world !” 3

4 4

5 fun main(): Public Int { 5 fun main(): Public Int {

6 Write(Alice, hello) 6 Write(Alice, “Hello, World”) 7 return tau * tau 7 return 6.28 * 6.28

8 } 8 }

圖 5-5:smcSL 前處理器針對常數區段處理範例

目前前處理器所能處理的運算式,包括整數與浮點數運算。這些運算可以 含有透過四則運算子表達的算式,其中也可以含有由 Ruby 數學相關函式參與的 運算式。這使得前處理器所處理的內容近於在 C/C++中可見的 Macro,但本研究 針對此部份採取的設計原則比較單純,希望這些功能只是輔助開發者可以更加 方便開發程式,並不希望成為像 Macro 那樣功能繁多的語言特色。

smcSL 編譯器目前也有參數,可以允許附帶輸出前處理器所處理完原始檔。

這個功能在要針對語法掃描與剖析器進行開發除錯時特別有用,因為事實上語 法掃描與剖析器必頇接受已經由前處理器處理完的格式,而非原始的 smcSL 格 式。所以在這種情境下就可以先將一份除錯用的 smcSL 檔案,透過該參數編成 語法掃描與剖析器可以接收的格式,然後透過系統直接執行該獨立部分的程式,

就不用在針對該部分除錯時還要等候後續編譯階段的流程執行完畢,所產生的 除錯訊息也會比較單純。這也是另一個將語法掃描與剖析器獨立出編譯器而帶 來的好處之一。

相關文件