1. 前言
“代碼可視化”的概念定義和業界案例在前文中已經進行了講述,綜述可閱讀淺析“代碼可視化”,更多相關知識可查看專欄“代碼可視化”。本文梳理了“代碼可視化”功能開發需要前置瞭解的編譯器前端部分知識,因能力有限若有解釋不清和錯誤的地方敬請諒解,如果想更深入正規的學習相關知識可以查看文後擴展閱讀。
2. 編譯器(Compiler)
主要了解前端和中端相關理論知識,後端部分和目標機器代碼、特定機器架構相關一般很少用到可視化中。本文主要講述前端部分內容,中端部分後面再另寫文章。
2.1 編譯器工作步驟
2.2 編譯器前端
2.2.1 詞法分析(Lexical Analysis,or Scanning)
2.2.1.1 理論知識
詞法分析又稱掃描(scanning),通過讀入組成源程序的字符流,將它們組織成爲有意義的詞素(lexeme)的序列。詞素是源程序中的最小語言單位,如關鍵字、標識符、常數、操作符和分隔符等。對於每個詞素,詞法分析器將產生對應的詞法單元(token)作爲輸出。
token:<種別碼,屬性值>
詞法分析器的核心邏輯基於有限自動機(Finite Automata),可以理解爲有限個狀態的自動執行機器,用來將掃描得到的字符映射到有限個的可能性上。類型包括:
整個自動構造過程見下圖,大致瞭解一下即可,如果想深入學習各種算法細節可自行查閱資料。
2.2.1.2 實踐一下
接下來我們練練手,使用Antlr對Java源碼進行詞法分析。Antlr是一個開源工具,支持根據規則文件生成詞法分析器和語法分析器,它自身是用 Java 實現的,Mac上可以使用Homebrew安裝或者直接使用idea插件antlr-v4。同時grammars-v4上提供了很多供參考的規則,我們這裏也直接使用其中針對Java8定義的詞法分析規則練手。
# 截取內容
- 關鍵字定義
ABSTRACT : 'abstract';
ASSERT : 'assert';
BOOLEAN : 'boolean';
BREAK : 'break';
BYTE : 'byte';
CASE : 'case';
CATCH : 'catch';
CHAR : 'char';
...
- 字符串字面量定義
StringLiteral: '"' StringCharacters? '"';
fragment StringCharacters: StringCharacter+;
fragment StringCharacter: ~["\\\r\n] | EscapeSequence;
...
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
# ① 編譯詞法規則
antlr Java8Lexer.g4
# ② 編譯上一步生成的java文件(注意需要把Antlr的JAR文件設置到CLASSPATH環境變量,否則會報錯)
javac Java8Lexer.java
# ③ 調用生成的詞法分析器獲取分析結果
grun Java8Lexer tokens -tokens ./examples/helloworld.java
2.2.2 語法分析(Syntactic Analysis, or Parsing)
2.2.2.1 理論知識
語法分析又稱解析(parsing),它在詞法分析後執行。它將tokens組織成語法結構,通常是一棵抽象語法樹(Abstract Syntax Tree, AST),這棵樹表示了源代碼的語法結構。語法分析器需要根據一組預定義的語法規則來分析詞法單元序列。這些規則通常以上下文無關文法(Context-Free Grammar, CFG)的形式定義,其中每個規則定義了語言中的一個結構如何由其他結構組成。
這裏先簡單說一下CFG,如果想深入學習可以再查查資料。一個上下文無關文法由以下四個部分組成:
-----舉個栗子-------
S → a S b
S → ε
-------解釋--------
·非終結符是S。
·終結符是a和b。
·產生式規則有兩條:S → a S b 表示 S 可以被 a S b 替換,S → ε 表示 S 可以被空字符串替換(ε 表示空字符串)。
·開始符號是S。
這個文法生成的語言是所有a和b的平衡字符串,例如:ab、aabb、aaabbb 等。
語法分析的核心能力是給定文法G和句子s,回答s是否能夠從G推導出來。實現的方式可以大致分爲自底向上和自頂向下的語法分析:
2.2.2.2 實踐一下
瞭解了基本概念後我們還是練練手,使用Antlr對Java源碼進行語法分析。這次就不使用grammars-v4中定義的語法規則了,因爲編程語言的語法規則比較複雜最後生成的AST可讀性比較差。
grammar PlayScript;
import CommonLexer; //導入詞法定義
/*下面的內容加到所生成的Java源文件的頭部,如包名稱,import語句等。*/
@header {
package antlrtest;
}
expression
: assignmentExpression
| expression ',' assignmentExpression
;
assignmentExpression
: additiveExpression
| Identifier assignmentOperator additiveExpression
;
assignmentOperator
: '='
| '*='
| '/='
| '%='
| '+='
| '-='
;
additiveExpression
: multiplicativeExpression
| additiveExpression '+' multiplicativeExpression
| additiveExpression '-' multiplicativeExpression
;
multiplicativeExpression
: primaryExpression
| multiplicativeExpression '*' primaryExpression
| multiplicativeExpression '/' primaryExpression
| multiplicativeExpression '%' primaryExpression
;
# ① 編譯語法規則
antlr PlayScript.g4
# ② 編譯上一步生成的java文件(注意需要把Antlr的JAR文件設置到CLASSPATH環境變量,否則會報錯)
javac *.java
# ③ 調用生成的語法分析器獲取分析結果(輸入表達式後使用^D觸發AST圖生成)
grun antlrtest.PlayScript expression -gui
age + 10 * 2 + 10
^D
2.2.3 語義分析(Semantic Analyzer)
2.2.3.1 理論知識
語義分析(semantic analyzer)使用語法樹和符號表中的信息來檢查源程序是否和語言定義的語義一致。它同時也收集類型信息,並把這些信息存放在語法樹或符號表中,以便在隨後的中間代碼生成過程中使用。語義規則一般包括但不限於:
由於每種語言都有其獨特的語義規則和特性,例如類型系統、作用域規則、合法的操作符重載等都是語言特定的。因此,語義分析必須針對每種語言的規範來設計。
2.2.3.2 實踐一下
由於不同語言的語義分析實現差異較大,沒有通用的語義分析器生成工具。因此我們直接來閱讀一下Java編譯器中的相關源碼,瞭解一下實現邏輯。javac中語義分析的源碼位於com.sun.tools.javac.code和com.sun.tools.javac.comp包中。
以下列舉了一些語義分析的類,源碼就不貼了,可以在langtools下載閱讀: