原文地址:https://blog.csdn.net/chaofanwei/article/details/25559029

1、LOOKAHEAD是什么

lookahead就是当语法分析器从词法分析器里取token时,需要取多少个才能让分析器正确的走下去。

例一

 void Input() :{}{"a" BC() "c"}void BC() :{}{"b" [ "c" ]}

在这个语法中,只能匹配“abc”和“abcc”。

假设我们现在把“abc”当成输入字符,来看javacc是如何工作的。

1、第一个字符是‘a’,这里没什么疑问,匹配“a”

2、读取下一个字符‘b’,毫无疑问进入BC(),刚好匹配‘b’

3、读取下一个自称‘c’,在这一步,我们看到了一个选择点,即是匹配['c']呢,还是跳出BC(),匹配最后一个‘c’。这里假定我们选择了[...],那么继续往下走。

4、因为现在我们已经跳出了BC(),但是Input说现在还需要一个‘c’,但我们已经没有字符了,因此宣告失败。

5、在遇到这种问题时,就说明我们在前面的选择点的地方可能选择了一个错误的决定,因此需要回溯到[...]

6、这个时候我们就应该选择Input里面的‘c’,这时候才能正确执行。

2、javacc里面的选择点

可以将javacc中choice point归结为以下几类:
  l . 由选择算子 | 引入的冲突,
 2. 由可省略算子 [] 或 ?引入的冲突
 3. 由重复算子 * 引入的冲突
 4. 由重复算子 + 引入的冲突

3、默认的选择点算法

看语法:

TOKEN [IGNORE_CASE] :
{<ID: (["a"-"z"])+>
}
void basic_expr() :
{}
{<ID> "(" expr() ")"  // Choice 1
|"(" expr() ")" // Choice 2
|"new" <ID>     // Choice 3
}

if (next token is "(") {
   choose Choice 2
 } else if (next token is "new") {
   choose Choice 3
 } else if (next token is <ID>) {
   choose Choice 1
 } else {
   produce an error message
 }

上面语法是没有什么冲突的,假如改成如下语法:

 void basic_expr() :{}{<ID> "(" expr() ")" // Choice 1|"(" expr() ")"  // Choice 2|"new" <ID>      // Choice 3|<ID> "." <ID>     // Choice 4}

就会报如下冲突信息:

arning: Choice conflict involving two expansions atline 25, column 3 and line 31, column 3 respectively.A common prefix is: <ID>Consider using a lookahead of 2 for earlier expansion.

意思就是说在默认的选择算法在这种情况下不能正确执行,因为1和4都是以ID开头的,这就是我们上面说的左因子

4、选择点冲突解决算法

1.修改语法,使之成为LL(1)语法。
 2.只要将LOOKAHEAD=k写到Options块中即可,javacc产生的分析器就可以分析LL(K)语法生成的语言
 
 采用第一种方法的好处是效率非常高,易于维护。采用第二种方法的好处是语法更加直观,但是却不易维护。有时候采用第一种方法是无法解决冲突的,第二种方法是唯一的选择。

5、设置全局LOOKAHEAD

全局的lookahead是在jj文件中的options中指定的,可以指定为非负整数,javacc自动生成LL(K)算法。这种方法是不提倡的,因为这会在每个选择点都进行LL(K)算法,即多向前看k个token,但大部分选择点都是一个(默认)就可以了。

假定这时把lookahead设置成2,那么在上面的3中的第二个文法就会变成:

当下来的两个token是<ID> 和"("时,那么选择点1,

如果下来的两个token是<ID> and "."时,那么就选择点4。

这样就能让上面的语法正常执行。

6、设置局部LOOKAHEAD

可以通过设置局部的lookahea方法,使语法分析器只在需要的时候向前看K个字符,别的情况下只用看一个就可以了,这种情况下,效率自然比通过全局设置好。

可以把上面语法改下:

void basic_expr() :{}{LOOKAHEAD(2)<ID> "(" expr() ")"  // Choice 1|"(" expr() ")"  // Choice 2|"new" <ID>      // Choice 3|<ID> "." <ID>     // Choice 4}

通过以上设置,只使第一个选择点使用LOOKAHEAD(2)。这种情况下工作逻辑如下:

 if (next 2 tokens are <ID> and "(" ) {choose Choice 1} else if (next token is "(") {choose Choice 2} else if (next token is "new") {choose Choice 3} else if (next token is <ID>) {choose Choice 4} else {produce an error message}

7、语法上的LOOKAHEAD

看语法:

void TypeDeclaration() :
{}
{ClassDeclaration()
|InterfaceDeclaration()
}

这里假定ClassDeclaration定义为在class的前面可以出现无数多次的public,final,而InterfaceDeclaration的定义也是前面可以出现出现无数多次的public,final。那么问题就出现了,因为当分析器在工作时,并不知道到底有多少个public或者fianl,也就不知道到底需要向前看多不个token,才能确定到底是选择ClassDeclaration还是InterfaceDeclaration。

显然简单的方法就是向前看无数多个,如下:

 void TypeDeclaration() :{}{LOOKAHEAD(2147483647)ClassDeclaration()|InterfaceDeclaration()}

但这样显示是不合理的,合理的做法应该是下面的方法:

 void TypeDeclaration() :{}{LOOKAHEAD(ClassDeclaration())ClassDeclaration()|InterfaceDeclaration()}

意思就是说,还是一直向前看,如果ClassDeclaration()能够匹配成功,则就用ClassDeclaration(),否则的话进入InterfaceDeclaration()。即:

 if (the tokens from the input stream match ClassDeclaration) {choose ClassDeclaration()} else if (<span style="color:#ff0000;"><strong>next</strong></span> token matches InterfaceDeclaration) {choose InterfaceDeclaration()} else {produce an error message}

当然还有一种优化的方法,见下:

void TypeDeclaration() :{}{LOOKAHEAD( ( "abstract" | "final" | "public" )* "class" )ClassDeclaration()|InterfaceDeclaration()}

工作过程就是:

 if (the nest set of tokens from the input stream are a sequence of"abstract"s, "final"s, and "public"s followed by a "class") {choose ClassDeclaration()} else if (next token matches InterfaceDeclaration) {choose InterfaceDeclaration()} else {produce an error message}

即如果下面的一些列token匹配( "abstract" | "final" | "public" )* "class",那么就选择ClassDeclaration(),否则选择InterfaceDeclaration()。

当然还有一种更加优化的方法,见下:

 void TypeDeclaration() :{}{LOOKAHEAD(10, ( "abstract" | "final" | "public" )* "class" )ClassDeclaration()|InterfaceDeclaration()}

在这种情况下,具体的工作过程就是,这时lookahead最多向前看10个token,如果超过10token后,还匹配( "abstract" | "final" | "public" )* "class"的话,那么就进入选择点ClassDeclaration()。

其实当不设置10的时候,默认的值就是最大值,即2147483647。

8、语义上的LOOKAHEAD

看语法:

 void Input() :{}{"a" BC() "c"}void BC() :{}{"b" [ "c" ]}

为了解决上面说到的回溯问题,我们可以使用如下的语法:

 void BC() :{}{"b"[ LOOKAHEAD( { getToken(1).kind == C && getToken(2).kind != C } )<C:"c">]}

意思就是:

 if (next token is "c" and following token is not "c") {choose the nested expansion (i.e., go into the [...] construct)} else {go beyond the [...] construct without entering it.}

首先把‘c’封装成一个label C,便于我们在lookahead里面引用它,即如果下来的第一个token是C和第二个不是C,那么选择[..],否则跳出BC。

其实上面的改写还可以使用下面的形式:

 void BC() :{}{"b"[ LOOKAHEAD( "c", { getToken(2).kind != C } )<C:"c">]}

即同时使用语法和语义上的lookahead。第一个c为语法上的lookahead,第二个为语义上面的lookahead。

9、LOOKAHEAD结构总结

通用的格式为:

 LOOKAHEAD( amount,expansion,{ boolean_expression })

amount:即设置向前看的token个数;

expansion:即指定的语法上的lookahead,

boolean_expression:即指定的语义上的lookahead。

格式中的3个部分,至少指定一个部分,如果同时出现多个部分,则使用逗号分隔。

javacc LOOKAHEAD关键字相关推荐

  1. JavaCC报错: JavaCC reported exit code 1: [-LOOKAHEAD=1, -STATIC=false

    1.美图 2.背景 Calcite中定制自已SQL解析器案例中,写好了文件,进行打包报错 报错信息如下 (base) lcc@lcc calcite-v120-self$ mvn clean pack ...

  2. Lucene学习总结之八:Lucene的查询语法,JavaCC及QueryParser

    一.Lucene的查询语法 Lucene所支持的查询语法可见http://lucene.apache.org/java/3_0_1/queryparsersyntax.html (1) 语法关键字 + ...

  3. Calcite JavaCC简单介绍及使用

    JavaCC 使用递归降低语法解析,LL(k).其中,第一个L表示从左到右扫描输入:第二个L表示每次都进行最左推导(在推导语法树的过程当中每次都替换句型中最左的非终结符为终结符.相似还有最右推导):k ...

  4. javacc编译原理实训报告

    一.综合训练目的与要求 本综合训练是软件工程专业重要的实践性环节之一,是在学生学习完<编译原理>课程后进行的综合练习. 本课综合训练的目的: 巩固和加深学生对编译原理课程基本知识的理解和掌 ...

  5. jjTree和javaCC学习笔记

    jjTree和javaCC学习笔记 javacc是类似lex/yacc的parser生成器,可以把一段文本转换为抽象语法树(AST). 一般来说,用户首先要写一个jjtree文件(如eg2.jjt), ...

  6. JAVACC 入门(转载)

    JAVACC 入门(转载) JavaFlashIDEA算法Unix  读了JavaCC自带文档中的SimpleExamples之后,有一点心得,于是总结一下,以备遗忘. JavaCC的输入文档是一个词 ...

  7. javacc 词法分析

    javacc 词法分析 词法分析器的功能和输出格式 词法分析器的功能是读入测试源程序,输出单词序列.词法分析器的单词常 表示成二元式:(单词种别码,单词在源代码中的字符串). 词法分析器需要把对象语言 ...

  8. 编译原理与javacc初探

    1.前序 真是书到用时方恨少啊,在大学的时候,虽然学过编译原理,但当时真是不懂啊,只是为了应付考试,死记硬背了一点点.现在呢,由于工作上的需要,不得不弥补一下啊. 这两天把编译原理的书又看了一遍,其实 ...

  9. c语言中external,static关键字用法

    static用法: 在C中,static主要定义全局静态变量.定义局部静态变量.定义静态函数. 1.定义全局静态变量:在全局变量前面加上关键字static,该全局变量变成了全局静态变量.全局静态变量有 ...

  10. java中实现具有传递性吗_Java中volatile关键字详解,jvm内存模型,原子性、可见性、有序性...

    一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作 ...

最新文章

  1. 神经网络AI加速器技术
  2. 《信息学奥赛一本通》 高精度乘法。输入两个正整数,求它们的积。
  3. Java最佳实践– Char到Byte和Byte到Char的转换
  4. 搞清axis的含义,这一篇就够了!
  5. 基于python的文件加密传输_基于python实现文件加密功能
  6. axios config里自定义属性,使用拦截器拦截,无法拿到自定义属性问题
  7. 算法导论第10章习题
  8. bzoj 1227 [SDOI2009]虔诚的墓主人
  9. 腾讯广告“虚拟IP”赛题突出重围,入选第七届“互联网+”双创大赛产业命题
  10. mysql中sysdate(),curdate(),curtime(),now()
  11. [译]记一次Kotlin官方文档翻译的PR(内联类)
  12. Springboot拼接实体类里面的URL
  13. 关于163邮箱,上传附件,本地验证文件大小的问题。
  14. 剑桥2021计算机专业,2021剑桥大学计算机专业录取条件出炉 你有信心过线吗
  15. PAT 甲级1021 Deepest Root
  16. 2006-10-30 18:37:00 著名Linux内核程序员大鹰 ox啊
  17. Oracle的深入学习
  18. CSS3——2D变形(CSS3) transform
  19. Spring事务不生效的原因
  20. linux基础知识(持续更新)

热门文章

  1. 【服务器数据恢复】服务器raid5磁盘阵列分区丢失的数据恢复案例
  2. 软件测试价值提升之路--第2部分“扫门前雪”-第3章“拦截缺陷”-读书笔记
  3. matlab普朗克黑体辐射公式,MATLAB 黑体辐射规律的研究详解.doc
  4. 桌面计算机文件夹图标没了,电脑文件夹图标不见了怎么办
  5. 数据库中的二维表—巧借Excel
  6. archlinux + dwm系统美化
  7. 163个人域名邮箱申请,163个人邮箱怎么注册创建
  8. php 过滤微信符号昵称,PHP处理微信昵称特殊符号过滤方法
  9. 2020校园招聘公司列表!计算机/互联网 技术类岗位!,一直更新!
  10. NLP 的巨人肩膀(下):从 CoVe 到 BERT