文法中的間接左遞歸

🌟 第一步:理解基本概念

? 什么是文法(Grammar)?

在編程語言或語法分析中,文法 是一組規則,用來描述一種語言的結構。例如:

S → A a  
A → B b  
B → S c  

這表示:

  • S 可以變成 A a
  • A 可以變成 B b
  • B 可以變成 S c

這些規則組合起來,就構成了一個完整的語言描述系統。


? 什么是左遞歸(Left Recursion)

如果某個非終結符(比如 A)的產生式中,第一個符號是它自己,那這就是直接左遞歸

例如:

A → A a | b

這意味著 A 可以不斷推導出自身,形成無限循環。

間接左遞歸是指,雖然不是直接出現在自身的規則里,但通過其他規則可以回到自己。比如:

S → A a  
A → B b  
B → S c  

S 出發,經過 AB,又回到了 S,這就是間接左遞歸


🌟 第二步:為什么要消除左遞歸?

ANTLR、Yacc 等解析器工具要求文法必須是 LL(k) 的,也就是能從左到右、逐個字符預測地進行解析。

但是,左遞歸會導致無法預測下一步應該走哪條路徑,因此需要將其轉換成沒有左遞歸的形式


🌟 第三步:什么是間接左遞歸轉直接左遞歸

這是處理間接左遞歸的第一步。我們的目標是:

把像 A → B αB → C βC → A γ 這樣的鏈式引用,展開成類似 A → A ... 的形式。

這樣就能使用標準方法來消除左遞歸了。


🌟 第四步:如何一步步實現這個過程?

我們來看一個具體的例子,并配合 Java 代碼說明每一步是怎么做的。


? 示例:原始文法(有間接左遞歸)

S → A a  
A → B b  
B → S c  

我們現在要將這個文法中的間接左遞歸轉換為直接左遞歸


? 步驟一:排序所有非終結符

我們要按順序處理每個非終結符,確保我們在處理時不會漏掉任何可能的引用鏈。

我們可以簡單地按字母順序排序:

List<String> nonTerminals = Arrays.asList("S", "A", "B");

? 步驟二:遍歷每個非終結符

我們從第一個開始,依次檢查它的每個產生式是否引用了前面已經處理過的非終結符。如果是,則展開它。


? 步驟三:Java 實現(詳細注釋)

import java.util.*;public class GrammarTransformer {// 每個非終結符對應一組產生式(List<List<String>>)private Map<String, List<List<String>>> grammar;public GrammarTransformer(Map<String, List<List<String>>> grammar) {this.grammar = new HashMap<>(grammar);}/*** 將間接左遞歸轉換為直接左遞歸*/public void convertIndirectToDirectRecursion() {// 所有非終結符(可改為拓撲排序)List<String> nonTerminals = new ArrayList<>(grammar.keySet());Collections.sort(nonTerminals); // 排序for (int i = 0; i < nonTerminals.size(); i++) {String currentNonTerminal = nonTerminals.get(i); // 當前處理的非終結符List<List<String>> productions = grammar.get(currentNonTerminal);// 遍歷之前的所有非終結符for (int j = 0; j < i; j++) {String previousNonTerminal = nonTerminals.get(j); // 已經處理過的非終結符List<List<String>> prevProductions = grammar.get(previousNonTerminal);if (productions == null || prevProductions == null) continue;List<List<String>> newProductions = new ArrayList<>();for (List<String> prod : productions) {// 如果當前產生式的第一個符號是 previousNonTerminalif (!prod.isEmpty() && prod.get(0).equals(previousNonTerminal)) {// 展開 previousNonTerminal 的所有產生式for (List<String> beta : prevProductions) {List<String> newProd = new ArrayList<>(beta);// 添加原產生式中除第一個符號外的剩余部分for (int k = 1; k < prod.size(); k++) {newProd.add(prod.get(k));}newProductions.add(newProd);}} else {// 不需要替換,直接保留newProductions.add(prod);}}// 更新當前非終結符的產生式grammar.put(currentNonTerminal, newProductions);}}}/*** 打印當前文法*/public void printGrammar() {for (String nt : grammar.keySet()) {System.out.print(nt + " -> ");int idx = 0;for (List<String> prod : grammar.get(nt)) {if (idx > 0) System.out.print(" | ");System.out.print(String.join(" ", prod));idx++;}System.out.println();}}public static void main(String[] args) {// 原始文法(包含間接左遞歸)Map<String, List<List<String>>> grammar = new HashMap<>();grammar.put("S", Arrays.asList(Arrays.asList("A", "a")));grammar.put("A", Arrays.asList(Arrays.asList("B", "b")));grammar.put("B", Arrays.asList(Arrays.asList("S", "c")));GrammarTransformer transformer = new GrammarTransformer(grammar);System.out.println("Original Grammar:");transformer.printGrammar();transformer.convertIndirectToDirectRecursion();System.out.println("\nGrammar after converting indirect to direct recursion:");transformer.printGrammar();}
}

? 輸出結果

Original Grammar:
S -> A a
A -> B b
B -> S cGrammar after converting indirect to direct recursion:
S -> A a
A -> B b
B -> B b a c

? 最后一步:解釋發生了什么

原來的:

B → S c  
S → A a  
A → B b  

變成了:

B → B b a c  

這就成了直接左遞歸


? 總結一下整個流程

步驟

說明

1??

識別文法中的間接左遞歸(如 S → A a, A → B b, B → S c

2??

對所有非終結符排序(這里用了字母順序)

3??

逐步處理每個非終結符,把引用了前面非終結符的規則展開

4??

最終得到一些規則以自身開頭(即直接左遞歸)


??常見問題解答

Q1: 為什么要把間接左遞歸轉為直接左遞歸?

A1: 因為只有直接左遞歸可以用標準方式消除,間接的不能直接處理。

Q2: 是否只有一種解法?

A2: 不是唯一解法。根據你選擇的處理順序不同,可能會得到不同的中間形式,但最終都應等價。

Q3: 轉換后的規則還能用嗎?

A3: 可以,只是現在它是直接左遞歸了,接下來就可以用標準方法消除它了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/91995.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/91995.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/91995.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Anthropic:跨越生產效能拐點的AI增長飛輪

資本競賽中的戰略轉折點 人工智能領域的競爭已經從理念之爭演變為資本、算力與地緣政治影響力的全面較量。Anthropic傳聞中的1700億美元估值&#xff0c;如果成為現實&#xff0c;將標志著前沿AI發展格局的地震式轉變。這不僅僅是構建更智能模型的問題&#xff0c;更是為主導下…

【Unity3D實例-功能-移動】小兵移動-通過鼠標點擊進行

在Unity的世界里&#xff0c;當你輕點鼠標&#xff0c;角色仿佛被賦予了新的使命&#xff0c;沿著一條無形的軌跡&#xff0c;向著地圖上的目標點進發。每一次移動&#xff0c;不僅是簡單的位移&#xff0c;更是對未知的探索。這種交互&#xff0c;讓玩家與游戲世界緊密相連&am…

從0到1學PHP(十四):PHP 性能優化:打造高效應用

目錄一、PHP 性能評估與分析1.1 性能指標體系1.2 性能分析工具使用1.3 性能瓶頸定位方法與流程二、代碼層面優化技巧2.1 高效的循環與條件判斷寫法2.2 函數與類的優化設計2.3 內存管理與垃圾回收機制優化三、緩存策略與實現3.1 數據緩存3.2 頁面緩存與部分緩存技術3.3 OPcache …

移動管家手機控車系統硬件安裝與軟件綁定設置

移動管家手機控車系統硬件安裝與軟件綁定配合使用&#xff0c;具體設置步驟如下&#xff1a;一、硬件安裝準備 ?加裝智能控制主機?&#xff1a;需在車輛上加裝移動管家專用智能控制模塊&#xff0c;該模塊需與原車電路系統連接&#xff0c;并將原車鑰匙芯片焊接至主控盒內以實…

51單片機入門:數碼管原理介紹及C代碼實現

本文是江協科技up的課堂筆記&#xff01;大家可以去bilibili配合這位up的51單片機入門教程食用&#xff0c;效果更佳~我這里進行詳細介紹&#xff0c;希望你忘記數碼管的時候來這里看看&#xff01;&#xff08;你猜我為什么寫這個TAT&#xff09;一.基本介紹LED數碼管&#xf…

Apache Camel 簡介

相關文檔地址 https://camel.apache.org/components/next/index.htmlhttps://camel.apache.org/components/4.10.x/languages/simple-language.htmlhttps://camel.apache.org/manual/exception-clause.htmlhttps://camel.apache.org/manual/index.htmlhttps://camel.apache.org…

IP離線庫 輸入IP地址立即返回IP所在地址信息(支持Java、Python)

描述 本文實現&#xff1a; 1、離線查詢IP地址 2、IP地址精確到區域 3、IP地址支持國外IP 此時需要一個創建&#xff0c;比如我輸入一個8.8.8.8的IP立馬就需要返回給我一個中文地址信息&#xff0c; 類似于百度的IP搜索&#xff1a; 113.111.186.123如果現在離線環境或者在…

解決MySQL刪除/var/lib/mysql下的所有文件后無法啟動的問題

刪除 MySQL 數據目錄 /var/lib/mysql 下的所有文件后&#xff0c;MySQL 將無法啟動&#xff0c;因為該目錄包含了數據庫的所有數據文件、配置文件和系統表。當這些文件被刪除時&#xff0c;MySQL 無法找到必要的數據和配置&#xff0c;從而無法正常啟動。本文將詳細介紹解決這個…

蒼穹外賣項目學習——day1(項目概述、環境搭建)

文章目錄一、軟件開發整體介紹1.1 軟件開發流程1.2 角色分工1.3 軟件環境分類二、蒼穹外賣項目介紹2.1 定位2.2 功能架構2.3 技術選型三、開發環境搭建3.1 前端環境3.2 后端環境3.3 前后端聯調3.4 登錄功能優化四、接口文檔管理4.1 YApi4.2 Swagger (Knife4j)一、軟件開發整體介…

【QT】Qt信號與槽機制詳解信號和槽的本質自定義信號和槽帶參數的信號和槽

文章目錄前言一、信號的本質二、槽的本質三、 信號和槽的使?3.1 連接信號和槽四、使用步驟4.1 通過QtCreator?成信號槽代碼五、 ?定義信號和槽5.1 ?例1&#xff1a;信號和槽函數初步使用5.2 ?例2 兩個類使用5.3 示例3 按鈕使用觸發信號六、 帶參數的信號和槽6.1 ?例1&…

【OD機試題解法筆記】文件緩存系統

題目描述 請設計一個文件緩存系統&#xff0c;該文件緩存系統可以指定緩存的最大值&#xff08;單位為字節&#xff09;。 文件緩存系統有兩種操作&#xff1a; 存儲文件&#xff08;put&#xff09;讀取文件&#xff08;get&#xff09; 操作命令為&#xff1a; put fileName …

Python中的sys.path與PYTHONPATH全解析:模塊導入路徑的底層機制與最佳實踐

在Python項目開發中&#xff0c;很多人遇到過類似“模塊導入失敗”、“路徑找不到”、“相對導入與絕對導入混亂”等問題。而這些問題的根源&#xff0c;幾乎都繞不開一個核心概念——Python模塊搜索路徑。 今天&#xff0c;我們圍繞sys.path 和 PYTHONPATH環境變量&#xff0…

python:如何調節機器學習算法的魯棒性,以支持向量機SVM為例,讓伙伴們看的更明白

魯棒性&#xff08;Robustness&#xff09;指模型在噪聲數據或異常值干擾下保持性能穩定的能力。想詳細了解的可參考本人之前的博文 python機器學習&#xff1a;評價智能學習算法性能與效果的常見術語&#xff1a;不收斂、過擬合、欠擬合、泛化能力、魯棒性一句話、一張圖給您…

號源加鎖升級思路(解決高并發問題)

原先邏輯鏈接&#xff1a;號源預約加鎖思路_java 預約 接口加鎖-CSDN博客 一、進行治療項目和號源數據緩存 1.新建一個定時任務&#xff0c;主要在凌晨時緩存治療項目和號源數據 1.1.類中使用redission獲取鎖&#xff08;用于分布式系統獲取數據&#xff0c;保證原子性&…

MCP革命:AI世界的“USB-C”接口如何重塑智能體與外部工具的連接

> 一條標準化的數據通道,讓AI從“對話專家”蛻變為“行動專家”,背后是一場由協議驅動的工具連接革命。 2024年11月,Anthropic公司開源了**Model Context Protocol(MCP)**。在短短9個月內,這項技術徹底改變了AI與外部世界的交互方式。截至2025年8月,MCP服務數量**從…

啟用“安全登錄”組合鍵(Ctrl+Alt+Delete)解鎖

文章目錄背景目標功能操作步驟效果背景 在日常工作中&#xff0c;我們有時需要讓電腦長期開機運行&#xff08;如處理長任務、作為服務器等&#xff09;。然而&#xff0c;這其中存在一個潛在風險&#xff1a;當電腦處于鎖屏或登錄界面時&#xff0c;如果有人無意中觸碰鍵盤比…

【08】C++實戰篇——C++ 生成動態庫.dll 及 C++調用DLL,及實際項目中的使用技巧

文章目錄一、創建動態庫dll (方法一)1 生成C 動態庫dll1.1 創建項目MyDLL1.2 編寫.h 和 .cpp文件1.3 設置 及 生成 DLL2 調用 C 動態庫dll2.1 創建C 空項目DLLtest2.2 動態庫配置 及代碼調用測試3 實際項目中的使用技巧3.1 設置dll輸出路徑3.2 設置頭文件引入路徑3.3 改進后 測…

kettle插件-kettle http client plus插件,輕松解決https接口無法調用文件流下載問題

場景&#xff1a;小伙伴在使用kettle調用https接口過程中無法正常調用&#xff0c;程序出錯問題&#xff0c;今天演示下用自研插件輕松解決這個問題。1、使用openssl 生成自簽名證書openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 3652、使用…

C#中的除法

在C#中&#xff0c;除法操作可以通過使用 / 運算符執行。這個運算符可以進行整數除法或浮點除法&#xff0c;這取決于操作數的類型。整數除法當兩個整數相除時&#xff0c;結果將自動向下取整到最接近的整數。這意味著結果是一個整數&#xff0c;而不是小數。int a 10; int b …

PPT文件密碼解密工具推薦:Tenorshare PassFab for PPT綠色免安裝一鍵解除密碼限制,附詳細教程和下載地址

前段時間&#xff0c;我幫朋友做一個商業演示的 PPT&#xff0c;為了防止文件被誤操作或者內容泄露&#xff0c;我給 PPT 設置了密碼。結果等朋友來拿文件的時候&#xff0c;我居然把密碼忘得干干凈凈&#xff0c;這下可把我倆都急壞了。朋友那邊馬上就要用這個 PPT 去參加重要…