詞法分析(Lexical Analysis) 是編譯的第一階段。詞法分析器的主要任務是讀入源程序的輸入字符、將他們組成詞素,生成並輸出一個詞法單元序列,每個詞法單元對應一個詞素。這個詞法單元序列被輸出到語法分析器進行語法分析。
知識儲備
詞法單元:由一個詞法單元名和一個可選的屬性值組成。詞法單元名是一個表示某種詞法單位的抽象符號,比如一個特定的關鍵字,或者代表一個標識符的輸入字符序列。詞法單元名字是由語法分析器處理的輸入符號。
模式:描述了一個詞法單元的詞素可能具有的形式。
詞素: 源程序中的一個字符序列,它和某個詞法單元的模式匹配,並被詞法分析器識別為該詞法單元的一個實例。
例如:count = count + increment;
可以表示成 <id, "count"> <=> <"id", "count"> <+> <id, "increment"> <;>
id 表示 identifier, 即標識符.
每一個 <> 部分都是一個詞法單元。 標識符的模式用正則表示如下:
letter_ -> A | B | ... | Z | a | b | ... | z | _
digit -> 0 | 1 | ... | 9
id -> letter_(letter_|digit)*
因此count, increment都是標識符的實例,即詞素.
類的結構如下:
類 Tag
類 Tag 定義了各個詞法單元對應的常量:
public class Tag { public static final int NUM = 256, ID = 257, TRUE = 258, FALSE = 259, AND = 260, EQ = 261, GE = 263, LE = 267, OR = 271, REAL = 272, NE = 273; }
類 Token
類 Token 有一個 tag 字段, 用於做出語法分析決定。
類 Num
類 Num 表示的是整數常量, 例如 29, 1
當在輸入流中出現一個數位序列時,詞法分析器將向語法分析器傳送一個詞法單元,該詞法單元包含終結符號 Num 及 根據這些數位計算得到的整數屬性值。如果把詞法單元寫成用 <> 括起來的元組,那么 29, 1 分別被表示成 <num, 29>, <num, 1>.
Num 繼承 Token, 增加了一個用於存放整數值的字段 value。
類 Real
與 Num 類似,用來表示實數。
類 Word
類 Word 繼承 Token, 增加了一個字段 lexeme, 用於保存關鍵字和標識符的詞素。
程序需要預讀一個字符,所以使用一個變量peek來保存下一個輸入字符, 同時規定: 要么保存了當前詞法單元詞素后的那個字符,要么保存空白符。
識別關鍵字和標識符
大多數程序設計語言使用 for、do、if 這樣的固定字符串作為標點符號,或者用於表示某種構造。這些字符串成為關鍵字 (keyword)。
關鍵字通常也滿足標識符的組成規則,因此我們需要某種機制來確定一個詞素什么時候組成一個關鍵字,什么時候組成一個標識符。如果關鍵字作為保留字,也就是說,如果他們不能被用作標識符,這個問題相對容易解決。此時,只有一個字符串不是關鍵字時,它才能組成一個標識符。
解決方案:
用一個表來保存字符串
Hashtable<String, Word> words = new Hashtable<String, Word>();
在初始化時在字符串表中加入保留的字符串以及他們對應的詞法單元。
reserve( new Word(Tag.TRUE, "true") );
reserve( new Word(Tag.FALSE, "false"));
當詞法分析器讀到一個可以組成標識符的字符串或詞素時,它首先檢查這個字符串表中是否有這個詞素。如是,它就返回表中的詞法單元,否則返回帶有終結符號 id 的詞法單元。
為了方便輸出,為每一個類 重寫 toString 方法
具體步驟:
首先跳過空白字符.
for (;; readch()) { if (peek == ' ' || peek == '\t') continue; else if (peek == '\n') line++; else break; }
接下來要處理數字。如果 peek 是數字,證明當前要處理的為數字常量,如果含有 小數點. 證明是實數,否則為整數.
if (Character.isDigit(peek)) { int v = 0; do { v = v * 10 + Character.digit(peek, 10); readch(); } while (Character.isDigit(peek)); if (peek != '.') return new Num(v); float x = v; float d = 10; for (;;) { readch(); if (!Character.isDigit(peek)) break; x += Character.digit(peek, 10) / d; d = d*10; } return new Real(x); }
處理標識符麻煩些,因為標識符的第一個字母不能是數字,所以如果第一個是字母就開始處理標識符。每次讀取一個字符或數字(第一個不能是數字),組成一個盡量長的字符串。如果該字符串在表中存在,則直接返回表中對應的 Word,如果不存在,新建一個 Word 為標識符類型, 並放到表中。
if (Character.isLetter(peek)) { StringBuilder b = new StringBuilder(); do { b.append(peek); readch(); } while (Character.isLetterOrDigit(peek)); String s = b.toString(); Word w = words.get(s); if (w != null) return w; w = new Word(s, Tag.ID); words.put(w.lexeme, w); return w; }
效果圖:
源碼: https://code.csdn.net/tanheaishui/simplelexicalanalyzer