簡體   English   中英

構建可組合的有向圖(Thompson 的掃描儀生成器構造算法)

[英]Building composable directed graphs (Thompson's construction algorithm for scanner generator)

我目前正在編寫基於Thompson 構造算法的掃描儀生成器,以將正則表達式轉換為 NFA。 基本上,我需要解析一個表達式並從中創建一個有向圖。 我通常將我的有向圖存儲為鄰接表,但這一次,我需要能夠非常有效地將現有有向圖組合成一個新的有向圖。 每次我讀到一個新字符時,我都無法復制我的鄰接表。

我正在考慮創建一個非常輕量級的 NFA 結構,它不會擁有自己的節點/狀態。

struct Transition {
  State* next_state;
  char transition_symbol;
};

struct State {
  std::vector<Transition> transitions;
};

struct NFA {
  State* start_state;
  State* accepting_state;
};

這將允許我簡單地重新分配指針以創建新的 NFA。 我所有的狀態都將存儲在一個中央位置(NFABuilder?)。 組合將通過外部函數完成,如下所示:

NFA create_trivial_nfa(char symbol) {
  State* start_state = new State();
  State* accepting_state = new State();
  start_state->transitions.emplace_back(accepting_state, symbol);
  // Something must own start_state and accepting_state
  return NFA{start_state, accepting_state};
}

NFA concatenate_nfas(NFA&& nfa0, NFA&& nfa1) {
  nfa0.accepting_state->transitions.emplace_back(nfa1.start_state, '\0');
  return NFA{nfa0.start_state, nfa1.accepting_state};
}

在這里,我將使用移動語義來明確 nfa0 和 nfa1 不再用作獨立的 NFA(因為我修改了它們的內部狀態)。

這種方法是否有意義,或者是否存在我尚未預料到的問題? 如果確實有意義,那么所有這些狀態的所有者應該是什么? 我也預計我的轉換會出現填充問題。 當打包在向量中時,轉換的大小為 16 字節,而不是 9 字節(在 64 位架構上)。 這是我應該擔心的事情還是只是大局中的噪音? (這是我的第一個編譯器。我正在關注Cooper & Torczon 的 Engineering a Compiler

Thompson 構造的本質在於它創建了一個具有以下特征的 NFA:

  1. 最多有2|R| 狀態,其中|R| 是正則表達式的長度。

  2. 每個狀態要么恰好有一個用字符標記的輸出轉換,要么最多有兩個 ε 轉換。 (也就是說,沒有狀態同時具有標記轉換和 ε 轉換。)

后一個事實表明,將一個狀態表示為

struct State {
  std::vector<std::tuple<char, State*>> transitions;
}

(這是您的代碼的略微縮寫)是一種非常高的開銷表示,其中開銷更多地與用於保存一兩個轉換的std::vector的開銷有關,而不是單個轉換的填充. 此外,上述表示沒有提供用於表示 ε 轉換的清晰技術,除非意圖是為 ε 保留一個字符代碼(從而使得無法在正則表達式中使用該字符代碼)。

更實際的表示可能是

enum class StateType { EPSILON, IMPORTANT };
struct State {
  StateType type;
  char      label;
  State*    next[2];
};

(該公式不存儲next的轉換數量,假設我們可以使用標記值來指示next[1]不適用。或者,我們可以設置next[1] = next[0];在這種情況下。記住它只對 ε 狀態重要。)

此外,由於我們知道不超過2|R| NFA 中的State對象,我們可以用小整數替換State*指針。 這將對可以處理的正則表達式的大小設置某種限制,但遇到千兆字節的正則表達式非常罕見。 使用連續整數而不是指針也會使某些圖算法更易於管理,特別是傳遞閉包算法,它是子集構造的基礎。

關於 Thompson 算法構建的 NFA 的另一個有趣的事實是,狀態的入度也限制為 2(同樣,如果有兩個過渡,則兩者都是 ε 過渡)。 這允許我們避免過早地創建子機器的最終狀態(如果子機器是連接的左側參數,則不需要)。 相反,我們可以只用三個索引來表示子機器:開始狀態的索引,以及最多兩個內部狀態的索引,一旦添加,它們將轉換到最終狀態。

我認為上述內容與 Thompson 的原始實現相當接近,盡管我確信他使用了更多優化技巧。 但是值得一讀 Aho, Lam, Sethi & Ullman(“龍之書”)的第 3.9 節,其中描述了優化狀態機構造的方法。

獨立於理論簡化,值得注意的是,除了關鍵字模式的 trie 之外,詞法分析中的大多數狀態轉換都涉及字符集而不是單個字符,而且這些集通常非常大,特別是如果詞法分析的單位是Unicode 代碼點而不是 ascii 字符。 使用字符集而不是字符確實會使子集構造算法復雜化,但它通常會顯着減少狀態計數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM