上一篇中实现的First函数没有考虑左递归,在这对此说明和实现

1.立即左递归

A -> Ab|a

1.两步或两步以上产生的左递归

A -> Bc|a

B -> Ab|d

前面的实现中,递归用first函数

public Set<Terminal> first(List<Symbol> tokens) {
first(token)
}
public Set<Terminal> first(Symbol token) {
first(tokens)
}

如果产生式存在左递归就会出现循环调用,得不到结果

那么需要做下面处理

1. 需要记录当前外层有没有处理这个符号,如果有则跳过

2. A -> Bc|a B->Ad | ε

1)first(A)

2)first(Bc)  并 first(a)

3)first(B) frist(c)  并  first(a)

4)first(Ad) frist(ε) first(c) 并 first(a)

5)first(A) first(d) frist(ε) first(c) 并 first(a)

可以看第5步又需要first(A),因为外层正在处理,所以直接返回,但是返回前需要判断A是否为空,

如果不为空,则直接返回,如果为空,则继续first(d)

看两组产生式的结果

A -> Bc|a B->Ad| d | ε               A的first集合是 a, d, c, B的first集合 a d c

A -> Bc|a B->Ad | d               A的first集合是 a, d, B的first集合 a d

可以看出  A -> Bc 对 frist(A)集合,如果A, B可以推出 ε 那么集合就多了c,否则 A->Bc 不影响 first(A)的结果

所以需要加过一个函数专门用来判断A是否可以推导出ε

/**
* tokens是否可以推导出ε
* @param tokens
* @param expands
* @return
*/
public boolean hasEpsilon(List<Symbol> tokens, Set<Symbol> expands) {
for (Symbol token : tokens) {
if (expands.contains(token)) {
return false;
}
if (Terminal.epsilon.equals(token)) {
return true;
}
if (token instanceof Terminal) {// 终结符直接返回
return false;
} expands.add(token);
boolean hasEpsilon = false;
List<Production> productionList = getProduction((Nonterminal) token);
for (Production production : productionList) {
if (hasEpsilon(production.getBody(), expands)) {
hasEpsilon = true;
break;
}
}
if (!hasEpsilon) {
return false;
}
expands.remove(token);
}
return true;
}

最终First集合函数修改如下

/**
* P140 FIRST(X)集合
* @param tokens 正在计算的串
* @param expands 用于记录正在计算的串是由哪个非终结符展开得到的
* @return
*/
public Set<Terminal> first(List<Symbol> tokens, Set<Symbol> expands) { // 是否全部包含ε
boolean allContainsEpsilon = true;
Set<Terminal> result = new HashSet<>();
for (Symbol token : tokens) { Set<Symbol> epsilonExpands = new HashSet<>();
boolean hasEpsilon = hasEpsilon(Arrays.asList(token), epsilonExpands); Set<Terminal> firstSet = first(token, expands);
if (firstSet == null) {
if (hasEpsilon) {
continue;
} else {
return result;
}
} else {
for (Terminal terminal : firstSet) {
if (terminal.equals(Terminal.of("ε"))) {
continue;
}
result.add(terminal);
} if (!hasEpsilon) {
allContainsEpsilon = false;
break;
}
}
} // 如果对于所有的j=1,2,...,k,ε在FIRST(Yj)中,那么将ε加入到FIRST(X)
if (allContainsEpsilon) {
result.add(Terminal.of("ε"));
} return result;
}

编译器-FIRST集合(补充:左递归)的更多相关文章

  1. 常用对象API、附加:集合补充

    基本数据类型对象包装类: 为了方便操作基本数据类型值,将其封装成了对象,在对象中定义了属性和行为丰富了该数据的操作. 用于描述该对象的类就称为基本数据类型对象包装类. byte——Byte short ...

  2. Python 之父的解析器系列之五:左递归 PEG 语法

    原题 | Left-recursive PEG grammars 作者 | Guido van Rossum(Python之父) 译者 | 豌豆花下猫("Python猫"公众号作者 ...

  3. python学习笔记:第14天 内置函数补充和递归

    一.匿名函数 匿名函数主要是为了解决一些简单需求而设计的一种函数,匿名函数的语法为: lambda 形参: 返回值 先来看一个例子: # 计算n的n次方 In[2]: lst = lambda n: ...

  4. python 内置函数补充 or 递归 or 二分法

    一.内置函数的补充 repr() 显示出字符串的官方表示形式 chr() print(chr(20013)) # 把数字编码转换成字符串 ord() print(ord('中')) # 20013 把 ...

  5. JavaSE 集合补充点(JDK1.9对集合添加的优化)

    通常,我们在代码中创建一个集合(例如,List 或 Set ),并直接用一些元素填充它. 实例化集合,几个 add方法调用,使得代码重复. public class Demo01 { public s ...

  6. Java 集合补充

    集合大致可以分List,Set,Queue,Map四种体系. 集合和数组不一样,数组元素可以是基本类型的值,也可以是对象(的引用变量),集合里只能保存对象(的引用变量). 访问:如果访问List集合中 ...

  7. 消除左递归c语言文法

    <程序> -〉 <外部声明> | <函数定义><外部声明> -〉<头文件> | <变量> | <结构体> <头 ...

  8. C程序语法(无左递归)

    <程序> -〉 <外部声明> | <函数定义><外部声明> -〉<头文件> | <变量> | <结构体> <头 ...

  9. java集合-补充HashMapJDK1.8

    在Java 8 之前,HashMap和其他基于map的类都是通过链地址法解决冲突,它们使用单向链表来存储相同索引值的元素.在最坏的情况下,这种方式会将HashMap的get方法的性能从O(1)降低到O ...

  10. C语言简易文法(无左递归)

    <程序> -〉 <外部声明> | <函数定义><外部声明> -〉<头文件> | <变量> | <结构体> <头 ...

随机推荐

  1. Vue3——集成 sass

    安装 sass npm install sass sass-loader -D 使用 scss 语法!!!需要加上 lang="scss" <style scoped lan ...

  2. 2024年2月中国数据库排行榜:PolarDB夺魁首登顶,TiDB攀升回探花

    银装素裹覆大地,春意初醒待来临.2024年2月墨天轮中国数据库流行度榜单出炉,表现最亮眼的无疑是PolarDB,其自23年7月以来一路高歌猛进,此次更是一举夺魁,彰显了云原生数据库的蓬勃发展态势,Oc ...

  3. Teradata退出中国,您可以相信中国数据库!

    继Adobe.Tableau.Salesforce之后,2023年2月15日,数仓软件巨头Teradata宣布将逐步结束在中国的直接运营.数仓界的"黄埔军校"仓皇撤出中国市场给出的 ...

  4. ORM的设计思想

    1 以面向对象的思想来完成对于数据库的操作! 2 万物皆对象

  5. 组件传参v-model语法糖只能写一次的解决办法

    v-model 的使用 解决只能使用一次v-model的问题:使用 sync 修饰符

  6. 013 Python 变量的内存管理(学点底层东西显得你异于常人)

    #!/usr/bin/env python # -*- coding:utf-8 -*- # Datatime:2022/7/18 21:13 # Filename:011 Python约定俗称的常量 ...

  7. Android复习(四)权限—>概览

    权限概述 许可 的目的是保护Android用户的隐私.Android应用必须获得访问敏感用户数据(例如联系人和SMS)以及某些系统功能(例如相机和互联网)的权限.根据功能的不同,系统可能会自动授予权限 ...

  8. KubeSphere 社区双周报|07.05-07.18

    KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...

  9. KubeSphere 社区双周报 | 开源之夏已启动 | 2023.04.28-05.11

    KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...

  10. vue 父子组件之间传值

    在vue中父子组件传值是必不可少的,大家必须要学会! 首先父组件向子组件传值: 父组件:比如我们传teacher为index,如果我们传值变量可以使用 : 绑定我们在data中定义的变量. 子组件:为 ...