JAVACC简介
编译原理实习_JavaCC
.jjt文件的方法声明(部分)
2
JavaCC所生成的类(jjt)
& SimpleNode.java:语法树节点的接口和实现
Node.java
/*AAA的start()会返回
语法树根节点的引用*/
SimpleNode n = AAA.Start();
/* SimpleNode的dump()可以输出语法树*/
注意:在1处添加的语义子程序往往用来实例化变量, 这些变量在语法分析过程中被使用;
String MultiplicativeExpression() : // 乘除表达式,注意返回值类型 { String first; String middle; String newTemp; Token op; } { first = UnaryExpression() // first是返回的字符串 { newTemp = first; } ( ( 对于之前的方法添加 op = "*" // op是Token对象 了语义子程序。 | op = "/" | op = "%" ) middle = UnaryExpression() // middle是返回的字符串 { newTemp = variableNameGenerator.genVariableName(); QTInfo qt = new QTInfo(op.image, first, middle, newTemp); qtTable.addQTInfo(qt); } )* { return newTemp; //返回临时变量字符串 “T1”,“T2”.. } }
1
JavaCC概述
JavaCC是一个词法分析器和语法分析器的生成器
JavaCC-Basic
11
© 2010 IBM Corporation
JavaCC语法
Options { LOOKAHEAD=2; } PARSER_BEGIN(SamplePars) public class SamplePars { public static void main(String args[]) throws ParseException { Samp parser = new Samp(System.in); System.out.print("Enter English: "); System.out.flush(); Try { parser.one_line(); } catch (ParseException x) { System.out.println("Exiting."); throw x; } } } PARSER_END(SamplePars) SKIP : { " " | "\r" | "\t" } TOKEN : /* OPERATORS */ { < I: "I" > | < YOU: "YOU" > | < HAVE: "HAVE" > | < A: "A" > | < BOOK: "BOOK" > | < BOOKS: "BOOKS" > | < PEN: "PEN" > } void one_line() : {} { ( <I> | <YOU> ) <HAVE> ( <BOOKS> | <A> <BOOK> | <A> <PEN> ) }
JavaCC学习与应用
编译原理之JavaCC学与用目录1,引言 (3)2,JavaCC简介 (3)3,作业要求 (3)设计要求: (3)词法分析器设计要求 (3)语法分析器设计要求 (4)4,文法修改 (6)5,程序设计 (7)选择项 (7)语法分析器类 (7)词法规则 (8)语法规则 (10)6,结果与分析 (11)附注1,JDK (13)附注2,JavaCC的获取、安装和使用 (14)1,引言这个学期,我们学习了编译原理这门课程,很多人都认为设计编译器是一项很复杂的任务,是语言设计师们的工作,但是实际上我们常常会面临需要自己设计编译器的场合。
所以利用这次机会,我学习了JavaCC这一项工具。
2,JavaCC简介JavaCC(Java Compiler Compiler)是一个用JAVA开发的最受欢迎的语法分析生成器。
这个分析生成器工具可以读取上下文无关且有着特殊意义的语法并把它转换成可以识别且匹配该语法的JAVA程序。
JavaCC可以在Java虚拟机(JVM) V1.2或更高的版本上使用,它是100%的纯Java代码,可以在多种平台上运行,与Sun当时推出Java的口号"Write Once Run Anywhere"相一致。
JavaCC还提供JJTree工具来帮助我们建立语法树,JJDoc工具为我们的源文件生成BNF范式(巴科斯-诺尔范式)文档(Html)。
注:JavaCC默认是支持LL(1)文法,但可以支持LL(k),只是在支持LL(k)时写一条设置语句(k>=2)。
3,作业要求设计要求:开发一个类Pascal语言的语法分析器,包括:词法分析器和语法分析器。
注:可以手动编写或用JavaCC自动生成一个语法分析器。
词法分析器设计要求设计一个词法分析程序,每调用一次就从源程序文件中顺序识别出一个单词符号,并返回该单词符号的内部编码、单词符号自身值、行号和列号。
遇到错误时能返回错误信息。
单词类型包括:整数、标识符、保留子、分隔符和运算符。
JAVACC简介
(2) 设定选项,并声明类 设定选项,
options { STATIC = false ; } PARSER_BEGIN(Calculator) import java.io.PrintStream ; class Calculator { static void main( String[] args ) throws ParseException, TokenMgrError, NumberFormatException { Calculator parser = new Calculator( System.in ) ; parser.Start( System.out ) ; } double previousValue = 0.0 ; } PARSER_END(Calculator)
如果我们想支持减法,则需要在词法分析 器中添加: TOKEN : { < MINUS : "-" > } 语法分析器应该变为: Expression -> Primary (PLUS Primary | MINUS Primary)*
Expression -> Primary (PLUS Primary | MINUS Primary)*
5
options { STATIC = false ; } PARSER_BEGIN(Adder) class Adder { static void main( String[] args ) throws ParseException, TokenMgrError { Adder parser = new Adder( System.in ) ; parser.Start() ; } } PARSER_END(Adder)
javacc+JJTree
一.JavaCC简介JavaCC(Java Compiler Compiler)是一个用JAVA开发的最受欢迎的语法分析生成器。
这个分析生成器工具可以读取上下文无关且有着特殊意义的语法并把它转换成可以识别且匹配该语法的JAVA程序。
JavaCC 可以在Java虚拟机(JVM) V1.2或更高的版本上使用,它是100%的纯Java代码,可以在多种平台上运行,与Sun当时推出Java的口号"Write Once Run Anywhere"相一致。
JavaCC还提供JJTree工具来帮助我们建立语法树,JJDoc工具为我们的源文件生成BNF范式(巴科斯-诺尔范式)文档(Html)。
二.JavaCC的特点JavaCC是一个用Java语言写的一个Java语法分析生成器,它所产生的文件都是纯Java代码文件,JavaCC和它所自动生成的语法分析器可以在多个平台上运行。
下面是JavaCC的一些具体特点:1.TOP-DOWN:JavaCC产生自顶向下的语法分析器,而YACC等工具则产生的是自底向上的语法分析器。
采用自顶向下的分析方法允许更通用的语法(但是包含左递归的语法除外)。
自顶向下的语法分析器还有其他的一些优点,比如:易于调试,可以分析语法中的任何非终结符,可以在语法分析的过程中在语法分析树中上下传值等。
RGE USER COMMUNTIY:是一个用JAVA开发的最受欢迎的语法分析生成器。
拥有成百上千的下载量和不计其数是使用者。
我们的邮件列表(https:///doc/mailinglist.html )和新闻组(pilers.tools.JavaCC)里的参与者有1000多人。
3.LEXICAL AND GRAMMAR SPECIFICATIONS IN ONE FILE:词法规范(如正则表达式、字符串等)和语法规范(BNF范式)书写在同一个文件里。
这使得语法易读和易维护。
4.TREE BUILDING PREPROCESSOR: JavaCC提供的JJTree工具,是一个强有力的语法树构造的预处理程序。
Calcite(一):javacc语法框架及使用
Calcite(⼀):javacc语法框架及使⽤ 是⼀个动态数据管理框架。
它包含许多组成典型数据库管理系统的部分,但省略了存储原语。
它提供了⾏业标准的SQL解析器和验证器,具有可插⼊规则和成本函数的可⾃定义优化器,逻辑和物理代数运算符,从SQL到代数(以及相反)的各种转换。
以上是官⽅描述,⽤⼤⽩话描述就是,calcite实现了⼀套标准的sql解析功能,⽐如实现了标准hive sql的解析,可以避免繁杂且易出错的语法问题。
并暴露了相关的扩展接⼝供⽤户⾃定义使⽤。
其提供了逻辑计划修改功能,⽤户可以实现⾃⼰的优化。
(害,好像还是很绕!不管了)1. calcite的两⼤⽅向 从核⼼功能上讲,或者某种程度上讲,我们可以将calicite分为两⼤块,⼀块是对sql语法的解析,另⼀块是对语义的转化与实现; 为什么要将其分为⼏块呢?我们知道,基本上所有的分层,都是为了简化各层的逻辑。
如果我们将所有的逻辑全放在⼀个层上,必然存在⼤量的耦合,互相嵌套,很难实现专业的⼈做专业的事。
语法解析,本⾝是⼀件⽐较难的事情,但是因为有很多成熟的编译原理理论⽀持,所以,这⽅⾯有许多现成的实现可以利⽤,或者即使是⾃⼰单独实现这⼀块,也不会有太⼤⿇烦。
所以,这⼀层是⼀定要分出来的。
⽽对语义的转化与实现,则是⽤户更关注的⼀层,如果说前⾯的语法是标准规范的话,那么语义才是实现者最关⼼的东西。
规范是为了减轻使⽤者的使⽤难度,⽽其背后的逻辑则可能有天壤之别。
当有了前⾯的语法解析树之后,再来进⼀步处理语义的东西,必然⽅便了许多。
但也必定是个复杂的⼯作,因为上下⽂关联语义,并不好处理。
⽽我们本篇只关注语法解析这⼀块功能,⽽calcite使⽤javacc作为其语法解析器,所以我们⾃然主关注向javacc了。
与javacc类似的,还有antlr,这个留到我们后⾯再说。
calcite中,javacc应该属于⼀阶段的编译,⽽java中引⼊javacc编译后的样板代码,再执⾏⾃⼰的逻辑,可以算作是⼆阶段编译。
javacc学习手册
1Javacc安装1.安装jdk,安装完成后设置环境变量PATH路径。
2.下载javacc,直接在其官方网站上下载,地址https://。
例如下载的是4.0版本的zip包javacc-4.0.zip。
3.解压缩javacc-4.0.zip到任意目录,将其bin目录设置为环境变量PATH路径。
4.运行如下命令查看是否正常安装Javacc versionJavacc copyright infojavacc option-settings inputfile2Javacc工具和生成分析程序的原理介绍2.1 三个工具(或可执行的命令) :1.javacc 用来处理语法文件(jj)生成分析器代码;JavaCC运行程序读入用JavaCC语法编写的源程序(后缀名为*.jj),即某一语言的词法规则、文法规则以及与该文法规则相联系的语义动作说明(Java代码段),就能生成Java代码的该文法的词法分析器和语法分析器。
而且,这些生成的程序在运行时不再需要JavaCC的任何支持。
2.jjtree 用来处理jjt文件,生成树节点代码和jj文件,然后再通过javacc生成解析代码;3.jjdoc 根据jj文件生成bnf范式文档(html)2.2 javacc的特性●自顶向下(TOP-DOWN):JavaCC生成自顶向下的分析器(分析程序),这是与YACC等工具所产生的自底向上的分析器所不同的。
●大的用户群(LARGE USER COMMUNITY):JavaCC是目前为止,最为流行的Java语言编写的分析器程序,并且已经有成千上万的使用者。
●词法&语法规范集成(LEXICAL AND GRAMMAR SPECIFICATION IN ONE FILE):像正则表达式、字符串等这样的词法规范和语法规范(.BNF)都将写在同一个文件中。
这将使得该语法规则更加容易阅读同时容易维护。
●语法树预处理器(TREE BUILDING PREPROCESSOR):JavaCC与JJTree同时发布,JJTree是一个功能强大的语法树预处理器。
javacc语法
javacc语法JavaCC(Java Compiler Compiler)是一个生成Java源代码的语法分析器,可以用于编译、解释和生成其他程序。
它可以生成词法分析器和语法分析器,这些工具可以用来处理和解析字符串、文件和其他数据。
以下是JavaCC的基本语法:1. 定义词法分析器:```javaTOKEN :{< ID : ('a'..'z' 'A'..'Z') ('a'..'z' 'A'..'Z' '0'..'9') >< NUMBER : ('0'..'9')+ >< STRING : '"' (~'"') '"' >}```这里定义了三个令牌类型:ID、NUMBER和STRING。
ID由小写和大写字母组成,可以包含数字;NUMBER由数字组成;STRING由双引号包围的字符串组成,其中不包含双引号。
2. 定义语法分析器:```javaPARSER_BEGIN(MyParser)public class MyParser extends SimpleParser {public static void main(String[] args) {MyParser parser = new MyParser();try {();} catch (ParseException e) {(());} catch (TokenMgrError e) {(());}}void Start() throws ParseException {}...}PARSER_END(MyParser)```这里定义了一个名为MyParser的解析器类,继承自SimpleParser类。
javacc链原理
javacc链原理CC链(Concurrency Control Chain)是一种用于解决并发控制问题的数据结构和算法,主要用于数据库系统中,以保证并发事务的正确执行。
在数据库系统中,多个并发事务可能同时对共享的数据进行读取和写入操作。
而如果没有适当的并发控制机制,就会导致数据的不一致性和执行结果的错误。
CC链便是为了解决这个问题而被提出的一种方案。
CC链的原理主要包括两个部分:基于时间戳的并发控制和基于锁的并发控制。
基于时间戳的并发控制主要通过为每个事务分配一个唯一的时间戳来实现。
当一个事务开始执行时,它会被分配一个比其他事务时间戳更大的时间戳。
这个时间戳可以用来表示事务的开始时间,也可以用来表示事务的读写操作执行的顺序。
在CC链中,每个事务会根据其时间戳被插入到合适的位置,以保证按照时间顺序执行。
当一个事务需要读取数据时,它会检查其他事务的时间戳,如果有时间戳比自己小的事务还在执行,则需要等待。
当一个事务需要写入数据时,它会检查其他事务的时间戳,如果有时间戳比自己小的事务还在执行,则需要等待。
基于锁的并发控制主要通过给数据项添加锁来实现。
数据库中的数据项可以被多个事务共享,因此在并发执行的情况下,会引发数据不一致性问题。
为了解决这个问题,CC链使用了基于锁的机制来保护共享数据。
在CC链中,每个数据项有两种锁状态:共享锁和排他锁。
当一个事务需要读取一个数据项时,它可以获得一个共享锁。
如果一个事务需要写入一个数据项时,它必须获得一个排他锁。
只有一个事务可以获得一个排他锁,而多个事务可以同时获得共享锁。
当一个事务需要获得一个数据项的锁时,如果这个锁已经被其他事务占用了,那么它将被放入等待队列中,等待相应的锁释放。
使用锁的机制可以保证每个事务在读写数据时的一致性,并且避免了竞态条件和冲突问题。
在CC链中,时间戳和锁两种并发控制机制是相互配合的。
时间戳机制可以保证事务的执行顺序,而锁机制可以保证数据的一致性和完整性。
JavaCC基本使用介绍
JavaCC基本使用介绍————————————————————————————————作者:————————————————————————————————日期:JavaCC基本使用介绍目录一、引言 (4)1.1 JavaCC简介: (4)1.2 词法分析器概念 (4)1.3 语法分析器概念 (5)1.4 JavaCC采取的语法分析方法 (5)二、示例使用流程 (8)2.1加法解析器 (8)2.2 扩展的加法解析器 (12)2.3 计算器解析器 (14)2015年1月2日星期五一、引言1.1 JavaCC简介:JavaCC(Java Compiler Compiler)是一个用JA V A开发的能生成词法和语法分析器的工具。
这个工具可以读取上下文无关且有着特殊意义语法的文本输入并把它转换成可以识别该语法的JA V A程序。
JavaCC可以在JVM 1.2或更高的版本上使用,它由纯Java代码开发,可以在多种平台上运行。
JavaCC还提供JJTree工具来帮助我们建立语法树,JJDoc工具为我们的源文件生成BNF范式(巴科斯-诺尔范式)文档(Html)。
1.2 词法分析器概念词法分析器又称扫描器。
词法分析是指将我们编写的文本输入流解析为一个个的记号(Token),分析得到的记号以供后续语法分析使用。
词法分析器的工作是低级别的分析,就是将一系列字符分成一个个的Token,并标记Token的分类。
例如:输入:int main() {return 0;}输出:“int”, “ ”, “main”, “(”, “)”, “ ”, “{”, “\n”, “\t”, “return”, “ ”, “0”, “;”, “\n”, “}”工作流程如图1所示:图1:词法分析器工作流程1.3 语法分析器概念语法分析(Syntacticanalysis or Parsing)是根据某种给定的形式文法对由单词序列构成的输入文本进行分析并确定其语法结构的一种过程。
javacc学习心得
简介:JavaCC是一个词法分析生成器和语法分析生成器。
词法分析和语法分析是处理输入字符序列的软件构件,编译器和解释器协同词法分析和语法分析来“解密”程序文件,不仅如此,词法分析和语法分析有更广泛的用途,我希望在本书列举的例子中能阐明这一点。
那么什么是词法分析器和语法分析呢?词法分析器可以将字符串解析为一个一个的标识符(Token),并且可以把这些标识符归类。
看一段C语言代码:C语言的词法分析器会把这段代码分解为如下的一些标识符:还会把这些标识符归类,在我们的例子中,这些标识符的类别为:其中EOF类别的标识符代表源文件的结束。
这些标识符出传送给语法分析器。
在C语言中,语法分析不需要所有的这些类别,在本例中SPACE类别的标识符不传送给语法分析器。
语法分析器会分析这些标识符,判断程序的结构。
在许多编译程序中,语法分析器用语法树来表示程序的结构。
编译器通过这棵树来产生代码。
看一个表达式:语法分析器基于语言规则分析这个表达式产生一棵树:如果输入没有语言的词法或语法,词法分析器或语法分析器都能产生错误信息。
JavaCC并不是一个词法分析器或者语法分析器,它只是一个生成器。
就是说,它读取文本后,基于一定的规则产生词法分析器和语法分析器的Java代码。
见图:进行词法分析和语法分析时必须要充分考虑各规则间的相互作用。
例如,在对 C语言的词法分析中,处理整型常量和浮点常量的代码是不能分开的,因为浮点数和整数的前面部分是一样的。
使用诸如JavaCC语法分析产生器,对整型常量和浮点常量是可以区分的,它们的共同点可在代码生成过程中提取出来。
这种模块性意味着JavaCC文件比直接的Java程序更容易写,更容易读,也更容易修改。
通过JavaCC 语法分析生成器的使用,软件工程师可以节省大量的时间,并且软件的质量也更高。
javaCC有三个工具:javaCC 用来处理语法文件(jj)生成解析代码;jjTree 用来处理jjt文件,生成树节点代码和jj文件,然后再通过javaCC生成解析代码;jjDoc 根据jj文件生成bnf范式文档(html)。
javacc学习心得
Javacc学习心得JavaCC是Java Compiler Compiler的缩写,是目前最流行,也是最广泛,最标准的JAVA下的词法语法分析器。
是对YACC(“Yet Another Compiler Compiler”)的继承(YACC 是AT&T 为了构建C 和其他高级语言解析器而开发的一个基于C 的工具)。
YACC 和其伙伴词法记号赋予器(tokenizer)——“Lex”——接收由常用的巴科斯-诺尔范式(Backus-Nauer form,又称Bacchus Normal Form,BNF)形式的语言定义的输入,并生成一个“C”程序,用以解析该语言的输入以及执行其中的功能。
JavaCC 与YACC 一样,是为加快语言解析器逻辑的开发过程而设计的。
但是,Y ACC 生成 C 代码,而JavaCC 呢,正如您想像的那样,JavaCC 生成的是Java 代码。
作为一种分析器,是一个用来对读取一段程序并把它转化为一段可是识别相匹配的相应规则的java程序的工具。
Javacc 可以同时完成对text的词法分析和语法分析的工作(使用的是LL算法分析工具),先输入一个按照它规定的格式的文件,然后javacc根据你输入的文件来生成相应的词法分析于语法分析程序.同时,新版本的Javacc除了常规的词法分析和语法分析以外,还提供JJTree等工具来帮助我们建立语法树。
同基于C语言下面的经典的词法分析和语法分析工具Lex 和yacc一样,javacc也是一个免费可以获取的通用工具,它可以在很多JAVA相关的工具下载网站下载,目前最新的版本是Version 4.1。
JavaCC可以在任何版本为1.2或更高的Java虚拟机上面运行。
它已经被认定是100%的纯Java性质。
JavaCC 已经在各种平台上测试和使用了,这是 Java语言“编写一次,到处运行”(WORA)特性的很好的诠释,这也是Java 给我们带来的好处之一。
JavaCC入门详解
从lex&yacc说到编译器(1.正则表达式)作者:tangl_99QQ:8664220msn:tangl_99@email:tangl_99@学过编译原理的朋友肯定都接触过LEX这个小型的词法扫描工具. 但是却很少有人真正把LEX用在自己的程序里. 在构造专业的编译器的时候,常常需要使用到lex和yacc. 正是因为这两个工具,使得我们编写编译器,解释器等工具的时候工作变得非常简单.不过话说回来,会使用lex 和yacc的人也确实不简单. Lex和yacc里面牵涉到一系列的编译原理的理论知识,不是简单地看看书就能搞懂的. 本文只是简单地介绍一下lex和yacc的使用方法.相关编译理请查看本科教材.国内大学教材里面对于lex和yacc的介绍很少,有些根本就没有,不过在国外的编译原理教材介绍了很多. 按照学科的分类,国内大学本科里面开的<<编译原理>>教程只是讲解编译的原理,并不讲解实践. 而对于实践方面则是另外一门学科<<编译技术>>. 关于编译技术的书籍在国内是少之又少. 前不久, 听说上海交大的计科内部出版过编译技术的教材.可惜我们这些人就无法得见了. 还好,机械工业出版社引进了美国 Kenneth C.Louden所著的经典著作<<编译原理及实践>>中,比较详细地介绍lex和yacc的使用.Lex属于GNU内部的工具,它通常都是gcc的附带工具. 如果你使用的Linux操作系统,那么肯定系统本身就有lex和yacc,不过yacc的名字变成了bison. 如果你使用的Windows操作系统,那么可以到cygwin或者GNUPro里面找得到. 网上也有windows版本lex和yacc,大家可以自己去找一找.本文一共有两篇,一篇是介绍lex,另一篇是介绍yacc. Lex和yacc搭配使用, 我们构造自己的编译器或者解释器就如同儿戏. 所以我把本文的名字叫做黄金组合.本文以flex( Fase Lex)为例,两讲解如何构造扫描程序.Flex可以通过一个输入文件,然后生成扫描器的C源代码.其实扫描程序并不只用于编译器 .比如编写游戏的脚本引擎的时候,我看到很多开发者都是自己写的扫描器,其算法相当落后(完全没有DFA的概念化), 甚至很多脚本引擎开发者的词法扫描器都没有编写,而是在运行过程中寻找token(单词). 在现代的计算机速度确实可以上小型的脚本引擎在运行中进行词法扫描, 但是作为一个合格的程序员, 或者说一个合格的计算机本科毕业生而来说, 能够运用编译原理与技术实践,应该是个基本要求.如果要说到词法分析的扫描器源代码编写, 其实也很简单, 会C语言的人都会写. 可是Kenneth Louden在<<编译原理及技术>里面,花了50多页,原因就是从理论角度,介绍标准的,可扩展的,高效的词法扫描器的编写. 里面从正则表达式介绍到DFA(有穷自动机),再到NFA(非确定性有穷自动机),最后才到代码的编写. 以自动机原理编译扫描器的方法基本上就是现在词法扫描器的标准方法, 也就是Lex使用的方法. 在Lex中,我们甚至不需要自己构造词法的DFA, 我们只需要把相应的正则表达式输入, 然后lex能够为我们自己生成DFA,然后生成源代码,可谓方便之极.本文不讲DFA, lex的输入是正则表达式, 我们直接先看看正则表达式方面知识就可以了.1.正则表达式(regular expression):对于学过编译原理的朋友来说,这一节完全可以不看.不过有些东西还是得注意一下,因为在flex中的正则表达式的使用有些具体的问题是在我们的课本上没有说明的.先看看例子:例1.1name Tangl_99这就是定义了name这个正则表达式,它就等于字符串Tangl_99.所以,如果你的源程序中出现了Tangl_99这个字符传,那么它就等于出现一次name正则表达式.例1.2digit 0|1|2|3|4|5|6|7|8|9这个表达式就是说,正则表达式digit就是0,1,2,…,9中的某一个字母.所以无论是0,2,或者是9…都是属于digit这个正则表达式的.“|”符号表示”或者”的意思.那么定义正则表达式 name Tangl_99|Running,同样的,如果你的源程序中出现了Tangl_99或者Running,那么就等于出现了一次name正则表达式.例1.3one 1*“*”符号表示”零到无限次重复”那么one所表示的字符串就可以是空串(什么字符都没有), 1, 11, 111, 11111, 11111111111, 11111111…等等.总之,one就是由0个或者N个1所组成(N可以为任意自然数).与”*”相同的有个”+”符号.请看下面的例子1.4例1.4realone 1+“+”符号表示”1到无限次重复”那么realone和one不同的唯一一点就是,realone不包含空串,因为”+”表示至少一次重复,那么realone至少有一个1.所以realone所表达的字符串就是1,11,111, 1111, 11111…,等等.例1.5digit [0-9]letter [a-zA-Z]这里的digit等于例1.2中的digit,也就是说,a|b|c就相当于[a-c].同理,letter也就是相当于a|b|c|d|e|f|…|y|z|A|B|C|D…|Z不过注意的一点就是,你不能把letter写成[A-z],而必须大写和小写都应该各自写出来.例1.6notA [^A]“^”表示非,也就是除了这个字符以外的所有字符所以notA表示的就是除了A以外的所有字符.下面让我们来看看一些一般高级程序语言中常用的综合例子.digit [0-9]number {digit}+letter [a-zA-Z_]digit前面多次提起过,就是0-9的阿拉伯数字.number就是所有的数字组合,也就是整数.Letter前面也提起过,唯一不同的就是多了一个下划线.因为一般我们的C语言中容许有下划线来表示定义的变量名,所以我也把下划线当成英语字母来处理了.这里number中使用上面定义的digit正则表达式.在lex中,用{digit}就是表示正则表达式digit.newline [\n]whitespace [ \t]+newline就是提行的意思.这里我们使用的是\n这个符号,它和C语言中表示提行号一致.问题是大家可能要问到为什么要使用[]符号.因为在lex中,如果你使用[],那么里面表示的肯定就是单个字符号,而不会被理解成”\”和”n”两个字符.Whitespace就是空格符号的意思.一般的高级程序语言中有两种,一种就是简单的空格,还有一种就是\t制表符.使用了”+”符号,就表示了这些空白符号的无限组合.从lex&yacc说到编译器(2.flex的使用)作者:tangl_99QQ:8664220msn:tangl_99@email:tangl_99@看了第一篇的关于正则表达式的说明后,下面我们就来通过它,使用flex这个词法分析工具来构造我们的编译器的词法分析器.关于lex的教程应该是很多,这里我就简单地介绍一下,然后着重后面的lex和yacc的配合使用以及其技巧.所以,如果你不看了后还是不太明白lex或者yacc的使用,请你自己上网去查查,这方面的教程是很多的.我知道的一篇常见的就是Yacc 与 Lex 快速入门Lex 与 Yacc 介绍它的作者就是Ashish Bansal.Flex就是fast lex的意思.而lex就是Lexical Analyzar的意思.flex可以在cygwin或者gnupro中找到.它是unix的一个工具,属于GNU组织产品.网上也可以找到单独可以在windows 下用的版本.我们一般把我们的词法扫描程序要扫描的一些单词(token)用正则表达式写好,然后作为lex的输入文件,输入命令flex xxx.l(xxx.l就是输入文件),lex经过处理后,就能得到一个名字叫lex.yy.c的C源代码.这个C源代码文件,就是我们的词法扫描程序.通常lex为我们生成的词法分析器的C源代码都是十分复杂而且庞大的,我们一般根本不会去查看里面的代码(放心好了,flex这个东西不会出错的)下面让我们看看几个我已经使用过的几个lex输入文件.这是一个前段时间我为GBA上的一个RPG游戏写的脚本引擎所使用的lex输入文件(部分)例2.1%{/* need this for the call to atof() below */#include <stdio.h>#include <stdlib.h>#include <math.h>#include "globals.h"%}digit [0-9]number ("-"|"+")?{digit}+hexnumber "0x"({digit}|[a-fA-F])+letter [a-zA-Z]identifier ({letter}|_)({number}|{letter}|_)* newline [\n]whitespace [ \t]+string \"[^"]*\"comment "#"[^#]*"#"%%{string} { return VM_STRING; } "Logo" { return VMIN_LOGO; }"FaceIn" { return VMIN_FACEIN; } "FaceOut" { return VMIN_FACEOUT; } "LoadTile" { return VMIN_LOAD_TILE; }"CreateRole" { return VMIN_CREATE_ROLE; } "ReleaseRole" { return VMIN_RELEASE_ROLE;} "CreateMap" { return VMIN_CREATE_MAP; } "ReleaseMAP" { return VMIN_RELEASE_MAP;} "ShowBitmap" { return VMIN_SHOWBITMAP; } "CreateDialog" { return VMIN_CREATE_DIALOG; } "ReleaseDialog" { return VMIN_RELEASE_DIALOG;} "Fight" { return VMIN_FIGHT; } "Delay" { return VMIN_DELAY; } "PressA" { return VMIN_PRESS_A; } "PressB" { return VMIN_PRESS_B; } "PressR" { return VMIN_PRESS_R; } "PressL" { return VMIN_PRESS_L; } "PressStart" { return VMIN_PRESS_START; } "PressSelect" { return VMIN_PRESS_SELECT;} {number} { return VM_NUMBER; } {whitespace} { /* skip whitespace */ } {identifier} { return VM_ID; } {newline} ;. ;%%int yywrap(){return 1;这里的lex输入文件一共有三个部分,用%%分开.第一部分中的%{和}%中的内容就是直接放在lex输出C代码中的顶部.我们通过它可以来定义一些所需要的宏,函数和include一些头文件等等.我的这个lex输入文件中也没什么特别的东西,就是常规的C源文件的include头文件%{/* need this for the call to atof() below */#include <stdio.h>#include <stdlib.h>#include <math.h>#include "globals.h"%}第一部分中,除了前面的%{和}%包含的部分,下面的就是正则表达式的定义.看了第一篇的正则表达式,这样你就能够在这里派上用场了.让我们来看看我这里定义的正则表达式:digit [0-9]number ("-"|"+")?{digit}+hexnumber "0x"({digit}|[a-fA-F])+letter [a-zA-Z]identifier ({letter}|_)({number}|{letter}|_)*newline [\n]whitespace [ \t]+string \"[^"]*\"comment "#"[^#]*"#"digit就不用说了,就是0-9的阿拉伯数字定义,第一篇文章中也举了这个例子.number就是digit的1到无限次的重复,再在其前面加上”+”和”-“符号.注意:“a”: 即使a是元字符,它仍是字符a\a: 当a是元字符时候,为字符aa?: 一个可选的a,也就是说可以是a,也可以没有aa|b: a或b(a): a本身[abc]: 字符a,b或c中的任一个[a-d]: a,b,d或者d中的任一个[^ab]: 除了a或b外的任何一个字符.: 除了新行之外的任一个字符{xxx}: 名字xxx表示的正则表达式这里需要特别说明的就是newline [\n]newline就是新行,这里我使用了[]把\n换行号括起来.因为如果我直接用\n表示的话,那么按照上面的规则,那就会看成\和n两个字符,所以我使用了[\n].有些时候newline也被写成[\n]|[\r\n].因为在文本文件中,一般换行一次,那么就是一个\n(0xA),可是在二进制文件中,换行有时候又是\r\n(0xD,0xA)一共两个字符号.第二部分就是定义扫描到正则表达式的动作.这些动作其实就是C代码,它们将会被镶嵌在lex输出的C文件中的yylex()函数中.上面的例子的动作其实十分平常,就是返回一个值.我们在外部使用这个lex为我们生成C代码的时候,只需要使用它的int yylex()函数.当我们使用一次yylex(),那么就会自动去扫描一个匹配的正则表达式,然后完成它相应的动作.这里的动作都是返回一值,那么yylex就会返回这个值.通常默认yylex返回0时候,表示文件扫描结束,所以你的动作中最好不要返回0,以免发生冲突.当然,动作中也可以不返回一值,那么yylex就会完成这个动作后自动扫描下一个可以被匹配的字符串,一直到扫描到文件结束.当扫描到一个可以被匹配的字符串,那么这个时候,全局变量yytext就等于这个字符串请大家一定记住这些正则表达式的顺序.如果出现一个字符串,可以同时匹配多个正则表达式,那么它将会被定义在前面的正则表达式匹配.所以我一般把字符串string定义在最前面.如果文件中的字符没有被lex输入文件中任何一个字符匹配,那么它会自动地被标准输出.所以大家一定要记住在每个正则表达式处理完毕后,一定要加上{newline}和.这两个正则表达式的动作.好,让我们看看lex为我们输出C文件中提供一些常量Lex 变量例2.2这是<<编译原理与实践>>书中配套的源代码的lex输入文件.大家可以参考一下,作者为它自己定义的一个Tiny C编译所做的词法扫描器./****************************************************//* File: tiny.l *//* Lex specification for TINY *//* Compiler Construction: Principles and Practice *//* Kenneth C. Louden *//****************************************************/%{#include "globals.h"#include "util.h"#include "scan.h"/* lexeme of identifier or reserved word */char tokenString[MAXTOKENLEN+1];%}digit [0-9]number {digit}+letter [a-zA-Z]identifier {letter}+newline \nwhitespace [ \t]+%%"if" {return IF;}"then" {return THEN;} "else" {return ELSE;} "end" {return END;} "repeat" {return REPEAT;} "until" {return UNTIL;} "read" {return READ;} "write" {return WRITE;}":=" {return ASSIGN;} "=" {return EQ;}"<" {return LT;}"+" {return PLUS;}"-" {return MINUS;} "*" {return TIMES;} "/" {return OVER;}"(" {return LPAREN;} ")" {return RPAREN;} ";" {return SEMI;} {number} {return NUM;} {identifier} {return ID;} {newline} {lineno++;} {whitespace} {/* skip whitespace */} "{" { char c;do{ c = input();if (c == EOF) break;if (c == '\n') lineno++; } while (c != '}');}. {return ERROR;}%%TokenType getToken(void){ static int firstTime = TRUE;TokenType currentToken;if (firstTime){ firstTime = FALSE;lineno++;yyin = source;yyout = listing;}currentToken = yylex();strncpy(tokenString,yytext,MAXTOKENLEN); if (TraceScan) {fprintf(listing,"\t%d: ",lineno);printToken(currentToken,tokenString);return currentToken;}这里有点不同的就是,作者用了另外一个getToken函数来代替yylex作为外部输出函数.其中getToken里面也使用了lex默认的输出函数yylex(),同时还做了一些其它的事情.不过我建议大家不要像作者那样另外写自己的结果输出函数,因为在后面,需要和yacc搭配工作的时候,yacc生成的语法分析程序只认名字叫yylex()的词法结果输出函数.if (firstTime){ firstTime = FALSE;lineno++;yyin = source;yyout = listing;}其中的yyin,yyout,source,listing都是FILE*类型.yyin就是要lex生成的词法扫描程序要扫描的文件,yyout就是基本输出文件(其实我们通常都不用yyout,即使要生成一些输出信息,我们都是自己通过fprintf来输出)."{" { char c;do{ c = input();if (c == EOF) break;if (c == '\n') lineno++;} while (c != '}');}其中,作者的这个Tiny C是以{}来包括注释信息.作者并没有写出注释信息的正则表达式,但是它可以通过检索“{”,然后用lex内部函数input()一一检查 { 后面的字符是不是 } 来跳过注释文字.(C语言的/* */注释文字正则表达式十分难写,所以很多时候我们都用这种方法直接把它的DFA(扫描自动机)写出来).本文就是通过简单地举出两个比较实际的例子来讲解flex输入文件的.再次说明,如果你是第一次接触lex,那么请看看前面我推荐的文章,你可以在IBM的开发者网上查到.下一篇关于yacc于BNF文法的说明也是如此.请大家先参考一下其它标准的教程.2003-9-30从lex&yacc说到编译器(3.范式文法)作者:tangl_99QQ:8664220msn:tangl_99@email:tangl_99@从这一节开始,我们就算进入编译器构造的正题了.不得不说,前面的词法扫描器在整个编译器部分只是个很小很小的组成,而这两节讲述的语言构造器才能真正为我们的编译工作起到重要的作用.这些东西相信大家在大学的编译原理的课程已经学了不少,那么本文我也只是大致地带过,让大家回忆起大学的知识,重要的yacc使用技巧等等,我将在后面的内容讲出.例3.1exp -> exp op exp | (exp) | numberop -> + | - | *这里就是一个定义的带有加法,减法,乘法的简单整数算术表达式的文法.其中粗体表示的是终结符号,也就是不能有产生式生成的符号.而exp,op就是非终结符,它们都是由一个”->”符号来产生的.比如100 + 222 *123123 –(888+11)就是符合上述文法的具体的表达式.注意,在文法定义中,是可以递归的.所以exp产生式右边的式子中可以再次出现exp.这里的|和正则表达式一样,表示的选择的意思,也就是说,exp可以是exp op exp或者(exp)再或者number.下面让我们看看<<编译原理及实践>>书中的一个关于BNF文法的介绍.比如说我们有个数学表达式(34-3)*42,然后我们来看看上面的exp文法怎么来推导识别它.(1) exp => exp op exp [exp ->exp op exp](2) => exp op number [exp ->number ](3) => exp * number [op -> * ](4) => (exp) * number [exp ->(exp) ](5) => (exp op exp) * number [exp ->exp op exp](6) => (exp op number)* number [exp -> number ](7) => (exp – number) * number [op -> - ](8) => (number–number)* number [exp -> number ]最终,exp里面全部的非终结符号全部变成了终结符号.那么推导完成.这种推导十分像我们在离散数学中讲到的命题推理.其实形式语言的推导的数学基础就是我们离散数学的命题推理.在推导过程中,其实就是把原来的文法中的递归展开.那么我们在推导的过程,也就很容易实现分析树的生成.而分析树就是我们编译程序中十分重要的信息源.我们之所以前面又做词法分析,又做语法分析的目标就是为了生成分析树.有了它,我们编译程序在后面的代码生成过程中将变得容易百倍.请看:例3.2同样是<<编译原理及实践>>书上的例子.设E -> E+a | a 表示的文法为G,那么考虑它生成的表达L(G)如果由标准的数学定义,那么我们用公式L(G)={s | exp =>* s }表示一种文法G.s代表记号符号的任意数组串,也就是我们的终结符号.而exp代表非终结符号,=>*表示一系列的从非终结符到终结符号的推导过程.这里*有点像我们在讲述正则表达式中的*符号一样,它表示0到无限次的重复.所以=>*就是表示0次到无限次的推导过程.L(G) = {a,a+a,a+a+a,a+a+a+a,…}E => E+a => E+a+a => E+a+a+a同时,在我们的编译课本上,又经常讲述另一种数学表达方式来阐述文法的定义.G=(T,N,P,S)注意,这里的T,N,P,S都是集合.T表示终结符号(terminal),也就是这里{a,+}N表示非终结符号(nonterminal),也就是这里{E},但是N不能与T相交.P表示产生式(production)或者文法规则(grammar rule)的集合,这里它只有一个元素: E -> E+aS表示集合N的开始符号(start symbol).关于S,本人也搞不清楚它的用处,所以很抱歉!例3.3这是我们C程序语言中经常使用if else文法statement -> if-stmt | otherif-stmt -> if(exp) statement | if (exp) statement else statementexp -> 0|1statement就是我们C语言中使用语句,它的产生式包括了两种可能,一是if-stmt语句,二是other.然后我们又另外定义if-stmt语句的产生式.这里有两种情况,一是没有else的,另外就是有else的.里面我们又使用了递归.if-stmt本来是包含在statement里面的,可是我们又在if-stmt的产生式中使用statement.正是因为文法中允许递归,所以它比起我们前面讲的正则表达式有更广泛的表示能力,但同时,文法的推导识别也更加法复杂.按照编译原理的书籍,一般讲完BNF文法后,就要重点讲解文法的推导算法.一共有两种,一是LL算法,自顶向下的算法,二是LR算法,自底向上的算法.LL算法比较简单,其中还有一种特殊的情况,就是我们下一节要讲的递归下降的算法.由于C语言中的函数本来就可以递归,那么实现这中递归下降的算法是十分简单的,而且对于我们一般的程序设计语言来说,虽然它的算法能力很弱,但是已经是足够用了.而关于LR的算法,那么就是一个大难题了.它的算法能力最强,但是实现起来十分困难,还好,已经有科学家为我们提供了yacc(或者叫bison)这个工具,可以来自动生成LR的文法推导算法.这就是我们一直在提到的yacc工具了.回过头来,我们看看下面的程序if(0) other else other的分析树思考: 为什么要把文法最终分析成树结构?因为文法本身是递归的,而表示的递归的最好数据结构就是树,所以我们把文法弄成树结构后,后面在处理代码生成等问题上,也可以用递归来很容易地完成.例3.4这里我给出microsoft在msdn中对于C语言的statement的文法注意,这里用:符号替代了我们前面产生式的->statement :labeled-statementcompound-statementexpression-statementselection-statementiteration-statementjump-statementtry-except-statement /* Microsoft Specific */try-finally-statement /* Microsoft Specific */jump-statement :goto identifier;continue;break;return expression opt;compound-statement :{declaration-list opt statement-list opt}declaration-list :declarationdeclaration-list declarationstatement-list :statementstatement-list statementexpression-statement :expression;optiteration-statement :while (expression)statementdo statement while (expression);for (expression opt;expression opt;expression opt)statement selection-statement :if (expression)statementif (expression)statement else statementswitch (expression)statementlabeled-statement :identifier:statementcase constant-expression:statementdefault :statementtry-except-statement : /* Microsoft Specific */__try compound-statement__except(expression)compound-statementtry-finally-statement : /* Microsoft Specific */__try compound-statement__finally compound-statement从lex&yacc说到编译器(4.文法识别(一))作者:tangl_99QQ:8664220msn:tangl_99@email:tangl_99@没想到这一系列文件能得到csdn和大家的这么看好,首先要感谢大家的赏识和csdn的推荐.那么我就更没有理由不写好这下面的几篇文章了.本来我的计划是简单把lex和yacc介绍完后就直接进入编译器的构造的技术细节问题讨论,但是最近看了一些国外经典教材后,发现文法的识别问题在编译原理和技术中是个绝不能忽视的问题.即使现在有了yacc工具来帮助我来识别文法,但是有些时候还是需要我们自己来写简单的语法分析器.1.什么是文法识别(语法分析)首先要告诉大家的是,这里的文法识别是指的上下文无关的文法,也就是上一节我们一直在讨论的那些 BNF式.比如说,我写了一句if (a>6+5) printf(“OK!”); else printf(“No!”);那么它匹配的文法也就是if-stmt -> if expr stmt| if expr stmt else stmt我们通常要为一个程序语言写出很多BNF式的文法,怎么知道这句话是匹配的哪个文法,这就是语法分析器(或者叫文法分析器要做的工作).知道了是那句文法后,我们才能对这句话做出正确的解释,所以文法识别是个不可忽视的工作.下面我来看看我们常使用的文法识别的算法.2.自顶向下的算法(LL算法)自顶向下的语法分析算法是十分简单的.自顶向下的算法也叫LL算法.LL(k)就是向前预测k个符号的自顶向下的算法.不过无论是我们国内的编译教程还是国外的经典教程都是只讨论了LL(1)算法.因为一般的程序语言,只使用LL(1)算法就已经足够了.这里我们同样也只是讨论LL(1)算法.其中有种特殊的算法叫做递归下降的算法,在C语言中,由于函数本身是可以递归的,所以实现这种算法就只需要写简单的几个函数的递归过程就是了.为什么叫自顶向下呢?因为在分析过程中,我们是从语法树的树顶逐步向树底分析的,所以叫自顶向下的算法.为了方便说明自顶向下算法的简单性,我们来看一下<<CompilersPrinciples,Techniques,and Tools>>中的一个例子.(本系列文章经常要引用国外经典著作的范例,希望大家不要告我抄袭,我实在找不到比大师的范例更经典的范例了)例4.1考虑一个Pascal中定义变量的文法.特别说明,这里的dotdot表示”..”type -> simple | id | array [ simple ] of typesimple -> integer|char| num dotdot num在为array[ num dotdot num] of integer构造一个分析数的时候,该算法就是从根结点开始.下面我们通过其中主要的三个步骤来看看算法的实现原理.第一步分析:_____________________________________________________________________________ _____首先分析的是输入的字符串第一个串”array”,判断出它属于type的First集合.所以在图中的分析树部分,我们的当前分析就是树根结点type.(图中标上箭头,就表示是当前正在分析的部分).这里遇到一个新名词:First集合.在大学里的编译课程肯定是讲过First集合的吧.不过我还是要在这里重复一次了.名词解释First集合:在对文法产生式进行判断的时候,每个产生式都是由好几个终结符和非终结符构成.比如本例中的文法type -> simple| id| array [ simple ] of typesimple -> integer|char| num dotdot num判断type的产生式的时候,如果我们把每个产生式里面的simple,id,array, [ ,simple ,] , of , type这些终结符和非终结符都进行判断的话,那么就会涉及到”试验和错误”的问题.当一个文法产生式分析到最后,发现并不匹配,就必然会产生回溯的问题,就要回到头,从新开始对第二个产生式逐步进行判断分析.我们知道,回溯的算法效率肯定是十分低效率的.但是实际上我们完全可以避免这种回溯算法,而完成同样工作的文法分析.这就产生了计算First集合的理论和以及后面的左提公因式的问题.First集合简单地说,就是一个非终结符的最开头的字符串(终结符号)的集合.比如说.First(simple) = { integer, char, num }First(type) = First(simple) U { id, array }这里的type的一个产生式中有个simple非终结符在其开头,那么simple的开头字符串同时也可以是simple,所以First(simple)也是First(type)的一部分.为什么我们只计算每个非终结符的最开头的终结符? 因为我们这里是考虑的LL(1)算法,LL(1)算法只向前预测一个字符号,所以我们只考虑一个First集合就可以判断出是哪个文法产生式了.这里听起来似乎有些不太可能,一个产生式有那么千百万化,如果单单只看第一个非终结符号,如果就能说明一个输入串到底是哪个产生式呢? 如果有两个产生式的最开头一样怎么办,比如像if语句,那怎么办? 但其实我们几乎所有的程序语言的文法都可以通过LL(1)来分析出来.原因是我们可以通过左提公因式来把最开头的相同的产生式的公共终结符号提取出来,留下两个子产生式,而他们的最开头的非终结符号不相同.左提公因式:例4.2考虑文法A -> ab|ac这里,A的两个产生式中最开头的终结符号都是’a’,那么就无法通过这个最开头的终结符号来判断一个输入串到底该是哪个产生式了.那么我们可以修改文法成A -> a A’A’-> b | c这样一来,一个文法变成两个,但是无论A还是A’,它们的每个产生式的First集合都是不相交的.所以,他们能够只通过最开头的终结符号来判断是哪个产生式.这个变化过程有点想我们的代数里面的 ab + ac = a(b+c),所以叫它左提公因式.这只是个简单的左提公因式的例子,实际当中还会遇到一些复杂的问题.但是无论是哪个编译教材,都会给出针对一切文法的左提公因式的算法.同样,计算First集合的算法也在教材中详细讲解了.我就不在这里再描述了.第二步分析:。
编译原理 JavaCC学习心的
JA V ACC 的研究和应用目录:1,Javacc的初步入门…………………………………………………………..1.1 Javacc的简介 (1)1.2 Javacc输出什么文件 (2)1.3 Token Manager (3)1.4 Javacc的安装 (4)1.5Javacc的原理 (5)2,Javacc的三个工具 (6)2.1javaCC的使用 (6)2.2jjTree的使用 (11)2.3jjDoc的使用 (14)3,Javacc的输入与输出文档 (15)4,jj文档的认识 (18)5,javacc中碰到冲突时,如何解决? (21)例如遇到Choice Confict怎么办? (22)6,JJTree的概述与节点的问题 (25)7,总结与归纳 (28)1.1,javacc简介javacc是做编译器用的工具,compiler's compiler ;javacc就是个语法分析器,可以自己构造合适的语法,用到的是上下文无关文法来做语法分析,可以自己添加相应的处理动作对语言的问题进行处理javacc可以用来生成语法分析器,相当于C中的yaccJava Compiler Compiler 是一个用JAVA开发的最受欢迎的语法分析生成器。
这个分析生成器工具可以读取上下文无关且有着特殊意义的语法并把它转换成可以识别且匹配该语法的JAVA程序。
它还提供JJTree等工具来帮助我们建立语法树。
JavaCC plug-in:一个用于辅助JavaCC应用程序开发的Eclipse插件JavaCC是一个java语言分析器,就是按照“模版”,“装配”不同的语言分析程序的源代码。
复杂语言的语法通常都是使用BNF(巴科斯-诺尔范式,Backus-Naur Form)表示法或者其“近亲”― EBNF(扩展的BNF)描述的。
自动化工具可以使用那些描述(我将使用通用的术语BNF来指代这两种变体)或与它们近似的描述来为你生成解析代码。
javacc 基础知识
javacc在简要讨论了语法、解析器和BNF 后,本文将介绍JavaCC,这是一个流行的解析器生成器工具。
您将开发使用JavaCC 的样本代码来构建定制的解析器,先从语法的BNF 描述开始。
第2 部分将接着演示如何使用辅助工具—JJTree 来构建同一解析的解析树表示,以及如何在运行时遍历该树,以发现其状态信息。
文章将以开发构建和遍历解析树的样本代码作为结束,该解析树是您为一小部分XQuery 语法生成的。
要完成最简单的日常解析任务,您不需要使用象自动化解析器生成器那样复杂的任何东西。
例如,同“梳理”CSV(逗号分割的值,Comma-Separated-Value)文件的各部分同样简单的编程练习需要了解文件的结构,可能还需要了解如何使用Java StringTokenizer。
另外,CSV 练习还需要了解很少的解析理论知识或者将自动化工具应用于任务的需求。
但是,一旦正式描述它的某种语言和语法变得复杂,那么语言中的有效表达式数量将迅速增加。
而能够手工处理将任意表达式解析成其组成部分(这或多或少是解析更简明的定义)所需的代码将变得越来越困难。
自动化解析器生成器减轻了这种困难。
其他程序员或许也可以将您生成的解析器用于他们自己的用途。
从BNF 开始复杂语言的语法通常都是使用BNF(巴科斯-诺尔范式,Backus-Naur Form)表示法或者其“近亲”— EBNF(扩展的BNF)描述的。
自动化工具可以使用那些描述(我将使用通用的术语BNF来指代这两种变体)或与它们近似的描述来为您生成解析代码。
本文就描述了这样的一种解析器-生成器工具,称为JavaCC。
我将简要地研究一下JavaCC 的基本知识,并在结束的时候花些时间研究一下它的一个辅助工具—JJTree,但是在讨论中不会介绍太多的理论知识,以免偏离主题。
本文力图阐明我的理念:您并不需要了解很多有关正规的解析理论就能进行解析!为什么使用JavaCC 呢?有几个原因:我对XQuery 有着强烈的兴趣,而W3C 的XML Query 工作组恰好使用JavaCC 来构建并测试XQuery 语法的版本,并且构建和测试它与XSL 组共享的XPath 语法。
JavaCC+入门
Java CC 入门1. Java CC 和分析器生成程序Java CC 是一个能生成语法和词法分析器的生成程序。
语法和词法分析器是字符串处理软件的重要组件。
编译器和解释器集成了词法和语法分析器来解释那些含有程序的文件,不管怎样,词法和预防分析器被广泛用于各种应用,所以我希望这本书中的示例能阐述清楚。
那么什么是词法和语法分析器呢?词法分析器能把一串字符切分成一溜叫做token 的子串并把它们归类。
看一个用C 语言写的小程序。
int main() {return 0 ;}C 编译器的词法分析器会把上面的程序切割成下面的一串token“int”, “ ”, “main”, “(”, “)”,“ ”, “{”, “\n”, “\t”, “return”“ ”, “0”, “ ”, “;”, “\n”,“}”, “\n”, “” .词法分析器还会识别每个token 的类型;在这个例子中这个token 串的类型可能是KWINT, SPACE, ID, OPAR, CPAR,SPACE, OBRACE, SPACE, SPACE, KWRETURN,SPACE, OCTALCONST, SPACE, SEMICOLON, SPACE,CBRACE, SPACE, EOF .EOF 这种token 表示输入文件结束。
Token 流将会传给分析器。
在这个C 语言的例子中,分析器并不需要所有的token;本例那些被归为SPACE 的 token 不会传给分析器。
分析器将会分析token 流以决定程序的结构。
通常在编译器中,分析器输出一棵代表程序结构的树。
这棵树将会作为编译器语法分析和代码生成的输入的一部分。
考虑某个程序里的一个语句:fahrenheit = 32.0 + 9.0 * celcius / 5.0 ;分析器根据语法规则分析这个表达式并生成一棵树:图1fahrenheit = 32.0 + 9.0 * celcius / 5.0 (译注:原文缺失该图,此图为译者根据前后语境所画)如果输入不遵循语言的词法或句法规则,词法分析器同时也负责产生出错信息。
javacc
JavaccJavaCC是一个词法分析生成器和语法分析生成器。
词法分析和语法分析是处理输入字符序列的软件组件,编译器和解释器协同词法分析和语法分析来解码程序文件。
词法分析器可以把一连串的字符序列划分成一个一个的叫做“Token”的子序列,同时它也可以把这些Token 分类。
这些Token序列将会传送给语法分析器以供其决定程序的结构。
Javacc编写的规则都写在一个.jj的文件夹中: , javaCC用来处理语法文件(jj)生成解析代码.1.文件基本规则:options{JDK_VERSION = "1.7";STATIC = false;}JDK_VERSION生成代码使用的jdk版本,static指是否生成静态的解析器类. Options的参数都是它们的默认值2.主类:PARSER_BEGIN(ParserDemo)com.synnex.ParserDemo;com.synnex.*;class ParserDemopublic static void main(String args []) throws ParseException{}}PARSER_END(ParserDemo)在PARSER_BEGIN和PARSER_END之间定义语法解析器的主类,这是整个解析程序的入口,里面主要有一些引用的包和类以及一个main方法(其他的方法由JavaCC生成)。
3.定义词法规则SKIP :{ /* white space */" "| "\t"| "\n"| "\r"}TOKEN :{ /* Operators and punctuation */<PERCENT: "%">| <AMPERSAND: "&">| <CARET: "^">| <VERTICAL_BAR: "|">| <TILDE: "~">| <QUOTE: "'">| <LEFT_BRACE: "{">}SKIP定义要忽略的字符串,TOKEN定义要识别的字符串。
面向对象语言JAVACC的区别与特点
面向对象语言JAVA/C#/C++的区别与特点面向对象是一种程序设计方法。
在面向对象概念中,整个世界由各种Object组成。
世界上存在着许多类型相同的对象,也存在着许多类型不相同的对象。
例如:一辆自行车和一根铅笔是类型不同的两个对象,李明的自行车和李芳的自行车可以看作是类型相同的两个对象。
早期的计算机程序设计语言经历了Machin e-Orient ed、Proced ure-Orient ed、机构化编程等阶段。
随着计算机技术发展,以及要解决的问题越来越复杂,早期的程序设计语言已经不能适应实际的需求。
从上世纪60年代末,陆续开发出了多个面向对象的程序设计语言。
面向对象程序设计语言的出现带动了面向对象的程序设计方法。
面向对象的程序设计是主流设计方法,目前绝大多数程序采用面向对象的思想来设计和开发的面向对象设计的主要特点:抽象(Abstra ction):抽象是把具体事物一般化的过程,对具有特定属性的对象进行概括,从中归纳出这类对象的共性,并从共同性的角度描述共有的状态和行为特征。
抽象包括数据抽象和方法抽象两个方面:数据抽象用来描述某类对象的共同状态;方法抽象用来描述某类对象的共同行为。
a封装性通过类实现封装。
编写大都通过创建类的对象,以对象为载体进行数据交流和方法执行。
主要目的:隐藏细节。
尽可能隐蔽对象的内部细节,对外形成一个边界〔屏障〕,只保留有限的对外接口使之与外部发生联系。
使用者只需要知道对象中变量和方法的功能,而不必知道行为实现的细节。
类的使用者与设计者是分开的。
封装原则在软件上的反映是:保护类中的数据,要求使对象以外的部分不能随意存取对象的内部数据(属性),从而有效的避免了外部错误对它的“交叉感染”,使软件错误能够局部化,大大减少查错和排错的难度。
JavaCC从入门到出门
JavaCC从⼊门到出门欢迎访问我的最新博客:⼀、JavaCCJavaCC是java的compiler compiler。
JavaCC是LL解析器⽣成器,可处理的语法范围⽐较狭窄,但⽀持⽆限长的token超前扫描。
安装过程:我是从github上down下来的zip压缩包,然后安装了下ant, 然后通过ant安装的javacc 1. ⾸先下载下来ant的源码,然后tar -zvxf apache-ant....tag.gz 解压缩,然后可以在解压出来的bin⽬录中看到ant的可执⾏⽂件 2. 从github下载javacc,进⼊解压缩的⽬录执⾏xxxxxx/ant。
然后会在target ⽬录中看到javacc.jar 包 3. 这个时候可以通过如下⽅法将jar包做成⼀个可执⾏⽂件: ⾸先创建⼀个shell脚本:#!/bin/shMYSELF=`which"$0"2>/dev/null`[ $? -gt 0 -a -f "$0" ] && MYSELF="./$0"java=javaif test -n "$JAVA_HOME"; thenjava="$JAVA_HOME/bin/java"fiexec "$java" $java_args -cp $MYSELF "$@"exit 1 命名为stub.sh,然后在jar包的所在⽬录执⾏: cat stub.sh javacc.jar > javacc && chmod +x javacc。
这样⼀个可执⾏⽂件就有了,不过在解析.jj⽂件时需要带⼀个javacc的参数,像这样: javacc javacc Adder.jj⼆、语法描述⽂件1、简介JavaCC的语法描述⽂件是扩展名为.jj的⽂件,⼀般情况下,语法描述⽂件的内容采⽤如下形式options {JavaCC的选项}PARSER_BEGIN(解析器类名)package 包名;import 库名;public class 解析器类名 {任意的Java代码}PARSER_END(解析器类名)扫描器的描述解析器的描述JavaCC和java⼀样将解析器的内容定义在单个类中,因此会在PARSER_BEGIN和PARSER_END之间描述这个类的相关内容。
JAVACC详解
JAVACC详解JavaCC(Java Compiler Compiler)是⼀个⽤JAVA开发的最受欢迎的⽣成器。
这个分析⽣成器⼯具可以读取上下⽂⽆关且有着特殊意义的语法并把它转换成可以识别且匹配该语法的JAVA程序。
JavaCC可以在Java(JVM) V1.2或更⾼的版本上使⽤,它是100%的纯Java代码,可以在多种平台上运⾏,与Sun当时推出Java的⼝号"Write Once Run Anywhere"相⼀致。
JavaCC还提供JJTree⼯具来帮助我们建⽴语法树,JJDoc⼯具为我们的源⽂件⽣成BNF范式(巴科斯-诺尔范式)⽂档(Html)下⾯是JavaCC的⼀些具体特点:1. TOP-DOWN:JavaCC产⽣⾃顶向下的,⽽YACC等⼯具则产⽣的是⾃底向上的语法分析器。
采⽤⾃顶向下的分析⽅法允许更通⽤的语法(但是包含左递归的语法除外)。
⾃顶向下的还有其他的⼀些优点,⽐如:易于调试,可以分析语法中的任何⾮终结符,可以在语法分析的过程中在语法分析树中上下传值等。
2. LARGE USER COMMUNTIY:是⼀个⽤JAVA开发的最受欢迎的⽣成器。
拥有成百上千的下载量和不计其数的使⽤者。
3. LEXICAL AND GRAMMAR SPECIFICATIONS IN ONE FILE:词法规范(如、字符串等)和语法规范(BNF范式)书写在同⼀个⽂件⾥。
这使得语法易读和易维护。
4. TREE BUILDING PREPROCESSOR: JavaCC提供的JJTree⼯具,是⼀个强有⼒的语法树构造的预处理程序。
5. EXTREMELY CUSTOMIZABLE:JavaCC提供了多种不同的选项供⽤户⾃定义JavaCC的⾏为和它所产⽣的的⾏为。
6. CERTIFIED TO BE 100% PURE JAVA:JavaCC可以在任何java平台V1.1以后的版本上运⾏。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
(3) 声明一个词法分析器
SKIP : { " " } TOKEN : { < EOL:"\n" | "\r" | "\r\n" > } TOKEN : { < PLUS : "+" > } TOKEN : { < NUMBER : (["0"-"9"]) + | (["0"-"9"])+ "." (["0"-"9"])+ | (["0"-"9"])+ "." | "." (["0"-"9"])+ > } TOKEN : { < NUMBER : <DIGITS> | <DIGITS> "." <DIGITS> | <DIGITS> "." | "." <DIGITS>> } TOKEN : { < #DIGITS : (["0"-"9"])+ > }
语法中的每一个非终结符都对应一个函数,函数调用 来表示非终结符之间的组成关系。
void Start() : {} { <NUMBER> ( <PLUS> <NUMBER> )* <EOF> }
11
5 编译adder.jj
Adder.java:语法分析器。其中的main函数是完全从 adder.jj中拷贝的,而start函数是被javacc由adder.jj描述 的规则生成的。 AdderConstants.java:一些常量,如PLUS, NUMBER, EOF等。 AdderTokenManager.java:词法分析器。 ParseException.java:用于在语法分析错误的时候抛出。 SimpleCharStream.java:用于将一系列字符串传入词 法分析器。 Token.java:代表词法分析后的一个个Token。Token 对象有一个整型域kind,来表示此Token的类型(PLUS, NUMBER, EOF),有一个String类型的域image,来表示 此Token的值。 TokenMgrError.java:用于在词法分析错误的时候抛出 12
(2) 设定选项,并声明类
options { } PARSER_BEGIN(Calculator) import java.io.PrintStream ; class Calculator { static void main( String[] args ) throws ParseException, TokenMgrError, NumberFormatException { Calculator parser = new Calculator( System.in ) ; parser.Start( System.out ) ; } double previousValue = 0.0 ; } PARSER_END(Calculator)
9
Javacc中的语法表示吸收了UNIX中正规文法的一些 记号 l 2 3 4 5 6 []: 其中的内容是可选的 +:前面的内容出现一次或多次 *: 前面的内容出现0次或多次 ?: 前面的内容出现0次或一次 |: 前面或后面 ():改变运算的优先级,把其中的内容作为一个 整体
10
4 声明一个语法分析器
从上面的例子,我们发现,把一个NUMBER取出, 并解析为整型这一步是可以共用的,所以可以抽象为 一个函数。
int start() throws NumberFormatException : { int i; int value ; } { value = getNextNumberValue() ( <PLUS> i = getNextNumberValue() { value += i ; } )* { return value ; } }
6
options { STATIC = false ; //生成非静态类 LOOKAHEAD=2//向前看2个字母,消除歧义用的 DEBUG_PARSER = true;//以debug形式生成,便于调试 } PARSER_BEGIN(Adder) class Adder { static void main( String[] args ) throws ParseException, TokenMgrError { Adder parser = new Adder( System.in ) ; parser.Start() ; } } PARSER_END(Adder) 7
8
第一二行表示空格和回车换行是不会传给语法分析 器的。 第三行声明了一个Token,名称为PLUS,符号为“+”。 第四行声明了一个Token,名称为NUMBER,符号 位一个或多个0-9的数的组合。 如果词法分析器分析的表达式如下: 123 + 456\n”,则分析为NUMBER, PLUS, NUMBER, EOF “123 - 456\n”,则报TokenMgrError,因为“-”不是一 个有效的Token. “123 ++ 456\n”,则分析为NUMBER, PLUS, PLUS, NUMBER, EOF,词法分析正确,后面的语法分析将 会错误。
int getNextNumberValue() throws NumberFormatException : { Token t ; } { t=<NUMBER> { return Integer.parseInt( t.image ) ; } }
第二个实例:计算器
(1) 生成一个calculator.jj文件 用于写入生成计算器词法分析器和语法分 析器的规则.
我们想要支持小数,则有四种情况:没有小数,小数 点在中间,小数点在前面,小数点在后面
(4) 声明一个语法分析器
要做计算器包含多行,每行都是一个四则运算表达式, 语法规则如下 : Start -> (Expression EOL)* EOF void Start(PrintStream printStream) throws NumberFormatException : {} { ( previousValue = Expression() <EOL> { printStream.println( previousValue ) ; } )* <EOF> } 注意: <EOF>也是一个Token表示文件结束
14
javacc采用的是自顶向下的分析方法,而且没有回溯功 能,因此如何解决冲突的问题,是程序员的责任 必须要解决两个问题:左递归和公因子 关于左递归的问题,一般采用经典的修改文法的方法 解决。关于公因子的问题,可以改写算法(提取公因 子),也可以通过展望(look ahead)更多符号的方法 来解决。 但是因为javacc扩展了经典的BNF,因此它面 临更多的问题。总之,当在编译时碰到冲突问题时, 就说到了一个choice point。
13
如果输入“123-456”则报如下异常
Exception in thread "main" org.apache.javacc.TokenMgrError: Lexical error at line 1, column 4. Encountered: "-" (45), after : "" at org.apache.javacc.AdderTokenManager.getNextToken(Ad derTokenManager.java:262) at org.apache.javacc.Adder.jj_ntk(Adder.java:148) at org.apache.javacc.Adder.start(Adder.java:15) at org.apache.javacc.Adder.main(Adder.java:8)
3.声明一个词法分析器
SKIP : { " " } SKIP : { "\n" | "\r" | "\r\n" } TOKEN : { < PLUS : "+" > } TOKEN : { < NUMBER : (["0"-"9"])+ > } TOKEN : { < NUMBER : <DIGITS> | <DIGITS> "." <DIGITS> | <DIGITS> "." | "." <DIGITS>> } TOKEN : { < #DIGITS : (["0"-"9"])+ > } 在词法声明部分,以#开头的token只是在词法分析时使 用,不能作为语法分析的输入,也就是说,它相对词 法分析是局部的
17
扩展语法分析器
在上面的例子中的start函数中,我们仅仅通过语法 分析器来判断输入的语句是否正确。 我们可以扩展BNF表达式,加入Java代码,使得经 过语法分析后,得到我们想要的结果或者对象。 我们将start函数改写为:
18
int start() throws NumberFormatException : { Token t ; int i ; int value ; } { t= <NUMBER> { i = Integer.parseInt( t.image ) ; } { value = i ; } ( <PLUS> t= <NUMBER> { i = Integer.parseInt( t.image ) ; } { value += i ; } )* { return value ; } } 注意:t.kind表示单词的类别,t.image表示单词值 19